From ca8539cf8b4f80e9a21be0aff782367493214498 Mon Sep 17 00:00:00 2001 From: florent giraud Date: Tue, 18 Jun 2024 10:49:56 -0400 Subject: [PATCH] docs: finalize missing documentation --- README.md | 466 +++++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 464 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 8b48193..bdb527c 100644 --- a/README.md +++ b/README.md @@ -102,8 +102,6 @@ Key Takeaways: To maximize benefits, we recommend using [tailwind merge](#adding-tailwind-merge-to-minify-the-string-generated) - - ## Adding Tailwind Merge to Minify the Generated String Our package does not optimize the class string size. As tailwind-merge is highly efficient for this purpose, we chose not to create another solution. This allows you to use tailwind-merge outside the design system and manage one version of it. @@ -183,16 +181,480 @@ export const twMerge = extendTailwindMerge(twMergeConfig); Then use it as the first example but instead of importing from `tailwind-merge` you would import from this file. +## className override + +Here the example + +```tsx +
+ Root element +
+``` + +You will see here the `className` we pass here. This property will always be place at the end of the string. In terms of pure css and selectors this has no impact but whats matter its where its positionned in your css. + +Tailwind is taking the order in count when creating its css. So we have decided to put className as a the latest override. We will explain in the next point how overrides are working with "How classes string is built" + +## How classes string is built + +Basically what you need to remember is this order: + +- slot values define in slots object +- variants definition if no props passed for a variant it will take the default variant value (take not that the order of variant is not reliable) +- if responsive variants responsive defintion +- compound variants +- You will see later but for specific use case you may have compound responsive +- className override + +If we have to create a mental class string it would be: + +`slot-class variant-class md:variant-class compound-classes md:compound-classes className` + ## Working with slots +Lets take our latest example and add one other slot. + +```ts +export const labelVariants = compose({ + "slots": { + "root": /** @tw */ "text-blue-500", + "otherSlot": /** @tw */ "text-blue-500" // here our new slot + }, + "variants": { + "size": { + "small": /** @tw */ "text-xs", + "large": { + root: /** @tw */ "text-7xl", + otherSlot: /** @tw */ "text-12xl" + } + }, + "fontWeight": { + xxl: { + "root": /** @tw */ "font-extrabold" + } + } + }, + "defaultVariants": { + "size": "small", + "fontWeight": "xxl" + } +})() +``` + +And lets update the usage + +```tsx + +const { root, otherSlot } = labelVariants + + return ( + <> +
+ Root element +
+
+ Other slot +
+ + ); +``` + +As you see its really easy to add and compose with slots as by default you will always have a root slot. + ## Working with compound variants +Compound variant are conditions to apply when you have multiple variant values. But you can also pass any values you want. + +Note that for compound variants as its an array we do apply the classes in the same order in the array. + +```ts +export const labelVariants = compose({ + "slots": { + "root": /** @tw */ "text-blue-500", + "otherSlot": /** @tw */ "text-blue-500" // here our new slot + }, + "variants": { + "size": { + "small": /** @tw */ "text-xs", + "large": { + root: /** @tw */ "text-7xl", + otherSlot: /** @tw */ "text-12xl" + } + }, + "fontWeight": { + xxl: { + "root": /** @tw */ "font-extrabold" + } + } + }, + "defaultVariants": { + "size": "small", + "fontWeight": "xxl" + }, + "compoundVariants": [ + { + "conditions": { + "size": "large", + }, + "class": /** @tw */ "bg-red-500 text-blue-500" // as soon as size is large it will apply this to all the slots + }, + { + "conditions": { + fontWeight: "xxl", + size: "small" + }, + class: { + otherSlot: /** @tw */ "bg-gray-500 border-red-500" // as soon as conditions are met it will apply this to only otherSlot + } + } + ] +})() +``` +By default if you do not pass `` you will have only auto complete on the variant property when using the slot function + +```tsx +
+``` + +If you want to have other props that are not variants you need to add the `` to be able to have the auto complete and not have typescript error. + +```tsx +
+``` + +Note: We have an existing issue when you are creating conditions the other props are not auto complete, only variants are. But you won't have typescript issues as it accept any other key: string / boolean + ## Working with responsive Variants +First you need to understand why we do have this feature. in SSR (server side rendering) when you render the page +to the user, you technically don't know the page of the user. And lets say you do mobile first. You can endup with some screen flickering if you do css in js. + +To solve that we have added this feature that will generate the good tailwind responsive values base on what you want. + +### Enable responsive + +Before looking at usage, you will need to add two things: + +1) add responsiveVariant definition + +```ts +export const labelVariants = compose({ + "slots": { + "root": /** @tw */ "text-blue-500", + "otherSlot": /** @tw */ "text-blue-500" // here our new slot + }, + "variants": { + "size": { + "small": /** @tw */ "text-xs", + "large": { + root: /** @tw */ "text-7xl", + otherSlot: /** @tw */ "text-12xl" + } + }, + "fontWeight": { + xxl: { + "root": /** @tw */ "font-extrabold" + } + } + }, + "defaultVariants": { + "size": "small", + "fontWeight": "xxl" + }, + "responsiveVariants": ["size"] +})() +``` + +this is important you add responsiveVariants so we will be able to generate tailwind classes. + +2) Update tailwind config to have safeList + +Safelist in tailwind is a way to force tailwind to add the classes we need to be able to handle responsivness. As tailwind is doing static analysis to add the classes to the output or not. In our case we will not have those available in the code so we need to add them via safeList options: + +```ts + +export const screens: Screens[] = ["sm", "md", "lg", "xl"] + +export default { + content: ["./index.html", "./src/**/*.{js,ts,jsx,tsx,mdx}"], + theme: { + extend: { + screens + } + }, + safelist: generateSafeList([labelVariants], screens) // import your variant definition the generateSafeList is taking care of the rest +}; +``` + +Note: that we have added a screens property here. Make sure to add it here also. We have an on going issue to make the screens overridable in typescript and also in the config. Adding inside the config is easy but we do not have typescript auto complete working yet. So for now please use the default tailwind screen definition + +3) Simple Usage + +Now everything is defined and you understand how it works lets see the usage: + +```ts +root({ + "size": { + "initial": "small", // this is mandatory to have an intiial option to respect mobile first approach + "md": "large" + }, + "fontWeight": "xxl" // this would not accept a responsive value as you didnt' define it in the responsiveVariants array +}) +``` + +With our latest definition + +```ts +"slots": { + "root": /** @tw */ "text-blue-500", + "otherSlot": /** @tw */ "text-blue-500" // here our new slot +}, +"variants": { + "size": { + "small": /** @tw */ "text-xs", + "large": { + root: /** @tw */ "text-7xl", + otherSlot: /** @tw */ "text-12xl" + } + }, + "fontWeight": { + xxl: { + "root": /** @tw */ "font-extrabold" + } + } +}, +"defaultVariants": { + "size": "small", + "fontWeight": "xxl" +}, +"responsiveVariants": ["size"] +``` + +This would produce a class string like + +`text-blue-500 text-xs md:text-7xl font-extrabold` + +As you see we do regroup the responsive values right after their initial values. + +4) Complex usage 1. How one responsive work with Compound + +here our definition with compound + +```ts +"slots": { + "root": /** @tw */ "text-blue-500", + "otherSlot": /** @tw */ "text-blue-500" // here our new slot +}, +"variants": { + "size": { + "small": /** @tw */ "text-xs", + "large": { + root: /** @tw */ "text-7xl", + otherSlot: /** @tw */ "text-12xl" + } + }, + "fontWeight": { + xxl: { + "root": /** @tw */ "font-extrabold" + } + } +}, +"defaultVariants": { + "size": "small", + "fontWeight": "xxl" +}, +"responsiveVariants": ["size"], +"compoundVariants": [ + { + "conditions": { + "size": "large", + }, + "class": /** @tw */ "bg-red-500 text-blue-500" + }, + { + "conditions": { + fontWeight: "xxl", + size: "small" + }, + class: /** @tw */ "bg-gray-500 border-red-500" + } +] +``` + +When we do have responsive props what we would do generally is creating an object to all breakpoints you have defined. + +Fill the values with their initial values if there is no other responsive props. + +So in our use case + +```ts +root({ + "size": { + "initial": "small", // this is mandatory to have an intiial option to respect mobile first approach + "md": "large" + }, + "fontWeight": "xxl", // this would not accept a responsive value as you didnt' define it in the responsiveVariants array, + className: "awesome-class" +}) +``` + +would become + +`text-blue-500 text-xs md:text-7xl md:bg-red-500 md:text-blue-500 bg-gray-500 border-red-500 awesome-class` + +5) Complex usage 2. How multiple responsive work with Compound + +here our definition with compound + +```ts +"slots": { + "root": /** @tw */ "text-blue-500", + "otherSlot": /** @tw */ "text-blue-500" +}, +"variants": { + "size": { + "small": /** @tw */ "text-xs", + "large": { + root: /** @tw */ "text-7xl", + otherSlot: /** @tw */ "text-12xl" + } + }, + "fontWeight": { + md: /** @tw */ "super-small" + xxl: { + "root": /** @tw */ "font-extrabold" + }, + } +}, +"defaultVariants": { + "size": "small", + "fontWeight": "xxl" +}, +"responsiveVariants": ["size", "fontWeight"], // note we have added also the fontWeight here +"compoundVariants": [ + { + "conditions": { + "size": "large", + }, + "class": /** @tw */ "bg-red-500 text-blue-500" + }, + { + "conditions": { + fontWeight: "xxl", + size: "small" + }, + class: /** @tw */ "bg-gray-500 border-red-500" + } +] +``` + +Usage: + +```ts +root({ + "size": { + "initial": "small", // this is mandatory to have an intiial option to respect mobile first approach + "md": "large" + }, + "fontWeight": { + "initial": "md", + "md": "xxl", + }, // this would not accept a responsive value as you didnt' define it in the responsiveVariants array, + className: "awesome-class" +}) +``` + +Before we show the ouput you need to understand how we will operate here. + +We do check if the conditions are met at the same breakpoint. If yes we put the breakpoint in front otherwise we do nothing. + +So to the conditions to met we would need here conditions with + +`size = large & fontWeight = md` or `size = large & fontWeight xxl` + +here our conditions + +```ts +{ + "conditions": { + "size": "large", + // as the fontWeight not defined here. we will take the initial + }, + "class": /** @tw */ "bg-red-500 text-blue-500" +}, +{ + "conditions": { + fontWeight: "xxl", + size: "large" + }, + class: /** @tw */ "bg-gray-500 border-red-500" +} +``` + +Here only the the second condition will met and its a `md` breakpoint. + +output + +`text-blue-500 text-xs md:text-7xl super-small md:font-extrabold md:bg-gray-500 md:border-red-500 awesome-class` + ## local development +Generally this is the way you will want to work + +Make sure install pnpm if you don't have it `npm i -g pnpm` + +at root folder: +- `nvm use` +- `pnpm install` + +going to core folder + +- `pnpm build -w` +- if you want to work with only unit tests. `pnpm test:unit` + +if you want to work with a "real world example" + +go to `ui` folder: +- `pnpm install` +- `pnpm build -w` + +And if you want to see a usage of the lib consuming `tailwind-buddy` + +go to `sandbox` folder: +- `pnpm install` +- `pnpm dev` + + ## Contributing +First easy contribution would be to help on improving the documentation. + +After that make sure to look at [good first issue label on github.](https://github.com/busbud/tailwind-buddy/issues?q=is%3Aopen+is%3Aissue+label%3A%22good+first+issue%22) + ## Benchmarks TCA is our lib.