Created
August 31, 2024 20:35
-
-
Save bengry/a11b9e5f19341ebdf42a42b68b728da4 to your computer and use it in GitHub Desktop.
Utility function for class organization when using libraries like Tailwind, which get very verbose very fast
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import { isPlainObject } from './isPlainObject'; | |
import { StringWithAutocomplete } from './StringWithAutocomplete'; | |
import { ClassValue as GenericClassValue, clsx } from 'clsx'; | |
type ClassesValue = string; | |
/** | |
* A utility function to allow easier authoring of Tailwind-based component classes. | |
* Usually Tailwind-based components are basically just a never-ending string of random classes (example: https://flowbite.com/docs/components/buttons/). | |
* | |
* This function aims to bring some sense and order to that, by allowing you to categorize classes into different | |
* categories, structured as an object, where the keys are the groups (and don't have any technical meaning), and the | |
* values are the classes related to that group. | |
* | |
* @example | |
* ```ts | |
* const button = classes({ | |
* reset: 'p-0 m-0', // classes related to CSS resets | |
* styles: 'bg-blue-500 text-white', // classes related to the style of the button | |
* layout: 'flex items-center justify-center', // classes related to the layout of the button | |
* hover: 'hover:bg-blue-600', // classes related to the hover state of the button | |
* active: 'active:bg-blue-700', // classes related to the active state of the button | |
* focusRing: 'focus:ring-2 focus:ring-blue-500', // classes related to the focus ring of the button | |
* }) | |
* | |
* // console.log(button) // logs 'p-0 m-0 bg-blue-500 text-white flex items-center justify-center hover:bg-blue-600 active:bg-blue-700 focus:ring-2 focus:ring-blue-500' | |
* ```` | |
*/ | |
export function classes<const T extends CategorizedClassValue>( | |
...values: ReadonlyArray<Partial<T>> | |
): ClassesValue { | |
return clsx( | |
values.map(value => (isPlainObject(value) ? Object.values(value) : value)) | |
); | |
} | |
type ClassDictionary = Record< | |
StringWithAutocomplete<ClassCategory>, | |
GenericClassValue | |
>; | |
type CategorizedClassValue = | |
| Exclude<GenericClassValue, Record<string, unknown>> | |
| ClassDictionary; | |
type ClassCategory = | |
| 'reset' | |
| 'variables' | |
| 'base' | |
| 'animation' | |
| 'disabled' | |
| 'focus' | |
| 'hover' | |
| 'active' | |
| 'focusRing' | |
| 'positioning' | |
| 'style' | |
| 'layout'; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
export function isPlainObject(obj) { | |
if (typeof obj !== 'object' || obj === null) return false | |
const proto = Object.getPrototypeOf(obj); | |
return proto !== null && Object.getPrototypeOf(proto) === null; | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
* A utility type around `string` that allows for autocomplete on the string on a specific set of values, yet accepting any string. | |
* | |
* @see https://twitter.com/diegohaz/status/1524257274012876801 | |
*/ | |
export type StringWithAutocomplete<S extends string> = | |
| S | |
| (string & Record<never, never>); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment