2.0.0 • Published 5 years ago

@taktikal/classnames v2.0.0

Weekly downloads
112
License
ISC
Repository
gitlab
Last release
5 years ago

@taktikal/classnames

This package follows the BEM naming convention for css loosely with utility classes mixed in for convenience. This package was created to make writing BEM classNames a bit faster and cleaner.

Input:

import classNames from "@taktikal/classnames";
import styles from "scss/Button.scss";

const s = classNames(styles);

s("button", {
  modifiers: {
    primary: true,
    accent: false,
    disabled: true,
  },
);

Output in development:

Button___button--[hash] Button___button--primary--[hash] Button___button--disabled--[hash]

Output in production:

[hash] [hash] [hash]

Configuration

Short version

Set localIdentName to [name]___[local]--[hash:base64:5] in dev and [hash:base64:5] in prod.

Optional configuration:

// _app.tsx

import { config } from "@taktikal/classnames";

config({ /* ... */ });

The options object for config:

interface {
  utilityStyles?: Styles;
  logWarnings?: boolean;
  onWarn?: (warning: string) => void;
}

Long version

Before you can use this package, the localIdentName for your css loader should look like this:

{
  localIdentName: process.env.NODE_ENV === "production"
    ? "[hash:base64:5]"
    : "[name]__[local]--[hash:base64:5]",
}

[name] is the filename, [local] is the classname and [hash] is well... a hash.

An example for the file

/* Button.scss */

.btn { /* ... */ }
.btn--primary { /* ... */ }

The resulting classNames would look like

{
  "btn": "Button___btn--[hash]",
  "btn--primary": "Button___btn--primary--[hash]"
}

The next.config.js with SCSS and TypeScript should look something like this:

const withSass = require("@zeit/next-sass");
const withTypescript = require("@zeit/next-typescript");
const ForkTsCheckerWebpackPlugin = require("fork-ts-checker-webpack-plugin");

module.exports = withTypescript(withSass({
  cssModules: true,
  cssLoaderOptions: {
    importLoaders: 1,
    localIdentName: process.env.NODE_ENV === "production"
      ? "[hash:base64:5]"
      : "[name]___[local]--[hash:base64:5]",
  },
  webpack: (config, options) => {
    // Add TypeScript type checking in terminal
    if (options.isServer) {
      config.plugins.push(new ForkTsCheckerWebpackPlugin());
    }

    // ...
  },
}));

Usage

classNames

import classNames from "@taktikal/classnames";

import styles from "src/scss/.../File.scss";

const s = classNames(styles);

...

<button
  className={s({
    name: "btn",
    modifiers: {
      primary: this.props.primary,
      accent: this.props.accent,
      disabled: this.props.disabled,
    },
  })}
  utils: ["f16", "mb-40"]
>
  {children}
</button>

// Or

<button
  className={s("btn", {
    modifiers: {
      primary: this.props.primary,
      accent: this.props.accent,
      disabled: this.props.disabled,
    },
  })}
  utils: ["f16", "mb-40"]
>
  {children}
</button>

If you are only using the className and modifiers with no utilities, you can use this shorthand.

<button className={s("btn", { primary: this.props.primary })}>
  {children}
</button>

If there are no modifiers or utility classes being used, you can just pass a string like this

<h1 className={s("card__title")}>Title</h1>

utilityClass

If you want to use utility classes, they have to be created in the format .u-{className} { ... }

.u-colorPrimary {
  color: $color-primary;
}

Then you will have to let @taktikal/classNames know of your utility classes, you should do this in the entry to your app (e.g. _app.js or index.js).

import { config } from "@taktikal/classnames";
import utilStyles from "~scss/Utils.scss";

config({
  utilityStyles: utilStyles,
  // ...
});

The utilClass function then takes in an array of utilities without the u- part of the className.

import { utilClass } from "src/utils/classNames";

<h1 className={utilClass(["mb-30", "colorPrimary"])}>Title</h1>

If the class only uses one utility class, you can use the shorthand

utilClass("mb-30");

The utility classes get applied first, then the className, then the modifiers.

For example:

<button
  className={s({
    name: "btn",
    modifiers: { primary: true },
    utils: ["mb-0"]
  })}
>
  {children}
</button>

The className in the code above would be something like

Utils__u-mb-0--[hash] Button__button--[hash] Button__button--primary--[hash]

Utility classes should be used when adding one or two simple things like text color, margin or padding. Avoid using utility classes heavily with BEM classNames as they can create a lot of noise.

Warnings

This structure allows us to do things like runtime validation of classNames in development. Here are some examples of warnings:

Button.scss: ClassName not found.
	The className: 'button' does not exist in file 'Button.scss'.
	Did you mean: 'btn'?

Button.scss: Modifier not found
	The modifier: 'disabled' does not exist on class 'btn'.
	A component reload (HMR or manual) will be required if the CSS is updated.
	
Utility class not found
	The utility class 'titleBorde' does not exist.
	Did you mean: 'titleBorder'?

You can enable them in the config:

import { config } from "@taktikal/classnames";

config({
  logWarnings: true,
  // ...
});

If you want to do something with the warnings, you can pass in a callback function

import { config } from "@taktikal/classnames";

config({
  logWarnings: true,
  onWarn: warning => {
    // Do stuff with warning
  },
  // ...
});