1.2.2 • Published 3 years ago

@jamsch/typed-tailwind-classes v1.2.2

Weekly downloads
-
License
MIT
Repository
github
Last release
3 years ago

typed-tailwind-classes

Typesafe tailwind classes for use in TypeScript projects

Prerequisites

  • Typescript >4.1

Installation

npm install --save @jamsch/typed-tailwind-classes

Usage

import type { TailwindClass } from '@jamsch/typed-tailwind-classes';

const blueBg: TailwindClass = 'bg-blue-500'; // OK

const extraBlueBg: TailwindClass = 'bg-blue-5000'; // Error!

Use the tw(...) helper to construct your class strings. One major difference with this helper (in comparison to classnames & clsx) is that modifiers (i.e. md:xxx, focus:xxx) are built using object syntax, as listed below:

import { tw } from '@jamsch/typed-tailwind-classes';

const condition = false;

const classes = tw(
  'bg-blue-500',
  'border-2',
  // Conditions
  condition && 'translate-x-10',
  !condition && 'translate-y-10',
  {
    // Format: [class name]: [boolean]
    'opacity-50': true,
    'animate-bounce': false,
    // Format: [modifier]: [class name]
    hover: 'border-4',
    'md:hover': 'opacity-100',
    // Format: [modifier]: ([class name]|false|undefined)[]
    'xs:hover': ['bg-blue-50', 'border-0', condition && 'animate-bounce'],
    'md:dark:focus': ['bg-red-100', 'animate-pulse'],
  },
);

console.log(classes);
// "bg-blue-500 border-2 translate-y-10 opacity-50 hover:border-4 md:hover:opacity-100 xs:hover:bg-blue-50 xs:hover:border-0 md:dark:focus:bg-red-100 md:dark:focus:animate-pulse"

Purge config

In order to avoid purging state modifiers that use the tw(...) function (e.g. tw({ "md:hover": "bg-blue-50" })), you'll need to modify your Tailwind's purge config to add in a transformer function for the files that use tw(...).

// tailwind.config.js

// @ts-check

/**
 * @param {string} content
 * @returns {string}
 */
const transformer = (content) => {
  // match content against the "tw(...)" function
  const twFunctionRegex = /tw\(([^)]+)\)/g;

  return content.replace(twFunctionRegex, (match, p1) => {
    // flatten any "{ [key]: string[] }" in tw(...) including newlines and append the object keys to each item in the array
    return (
      p1
        .replace(/\n/g, '')
        // Match `[modifier]: ["item_one", "item_two"]`
        .replace(/([^\s]+):\s*?\[([^\]]+)\]/g, (match, modifierKey, stringValues) => {
          // modifierKey = "md" | `"md:hover"` | "`sm:focus`", etc
          // remove all quotes from key
          const modifier = modifierKey.replace(/["']/g, '');

          // prepend modifier with each value in the array
          // e.g. `"hello", "world"` => `"hover:hello", "hover:world"`
          const modifierWithValue = stringValues.replace(/["']([^\s]+)["']/g, `"${modifier}:$1"`);

          return modifierWithValue;
        })
        // Match `[Object key]: \"string_value\"`
        .replace(/([^\s]+):\s*?"([^\"]+)"/g, (match, key, value) => {
          const modifier = key.replace(/["']/g, '');
          return `"${modifier}:${value}"`;
        })
        // Flatten the object
        .replace(/[\{\}]/g, '')
    );
  });
};

// Install @types/tailwindcss for the below "@type" annotation to work correctly

/** @type {import("tailwindcss/tailwind-config").TailwindConfig} */
module.exports = {
  mode: 'jit',
  purge: {
    // location to your files
    content: ['./components/**/*.{tsx,ts}', './pages/**/*.{tsx,ts}'],
    transform: {
      tsx: transformer,
      ts: transformer,
      // NOTE: place any extra file extensions here
    },
  },
};

Extending config

Since Tailwind allows you to add additional colors, screens and other properties, you need to provide this library the list of additions you've made. You can do this by importing the createTw class and providing a list of additional colors, screens and other options.

Currently, the list of supported config options are the following:

type ExtendConfig = {
  animation?: string;
  backgroundImage?: string;
  colors?: string;
  fontFamily?: string;
  opacity?: string;  
  screens?: string;
};

Example

// tw.ts
import { createTw } from '@jamsch/typed-tailwind-classes';

type ExtendedConfig = {
  fontFamily: 'merriweather' | 'noto-sans';
  colors: 'maroon' | 'navyblue';
  animation: 'spin-360';
  opacity: '15' | '35' | '65';
  screens: 'portrait' | 'tablet';
};

export default createTw<ExtendedConfig>();

In another file:

import tw from './tw';

const classes = tw('font-noto-sans', 'bg-maroon', 'bg-opacity-15', 'opacity-35', {
  portrait: 'border-0',
  'portrait:hover': ['animate-spin-360', 'border-2'],
  'tablet:dark': 'bg-navyblue',
}); // "font-noto-sans bg-maroon bg-opacity-15 opacity-35 portrait:border-0 portrait:hover:animate-spin-360 portrait:hover:border-2 tablet:dark:bg-navyblue"

Alternatively, you can use BuildTailwindClasses and BuildVariants to generate the class names.

import type { BuildTailwindClasses, BuildVariants } from '@jamsch/typed-tailwind-classes';

type ExtendedConfig = {
  colors: 'maroon' | 'navyblue';
  animation: 'spin-360';
  opacity: '15' | '35' | '65';
  screens: 'portrait' | 'tablet';
};

type ExtendedTailwindClasses = BuildTailwindClasses<ExtendedConfig>;
type Variants = BuildVariants<'portrait' | 'tablet'>;

// Warning: This will probably not work, and also isn't a great idea for performance
type AllClassesAndVariants = ExtendedTailwindClasses | `${Variants}:${ExtendedTailwindClasses}`;

const maroon: ExtendedTailwindClasses = 'bg-maroon'; // OK
const blue: ExtendedTailwindClasses = 'bg-blue-100'; // OK
const maroonTwo: ExtendedTailwindClasses = 'bg-maroon-2'; // Error!
1.2.2

3 years ago

1.2.0

3 years ago

1.2.1

3 years ago

1.1.0

3 years ago

1.0.1

3 years ago