npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

better-classnames

v1.0.1

Published

advanced utility for programmatically resolving classnames

Downloads

7

Readme

Better-classnames - Advanced classnames utility

better-classnames is a lightweight and advanced utility allowing for programmatically and conditionally building classnames. Building upon existing work with added configuration and overriding support.

Installation

Install by simply running:
npm install better-classnames.

Polyfills may be required for older browsers:
Array.isArray: see here for details
Object.keys: see here for details

Usage

Importing

import classnames from "better-classnames";
// or
const classnames = require("better-classnames");

Simple

Accepts a set of strings, objects or lists as arguments

  • strings are simply concatenated with spaces
  • lists are flattened and concatenated with spaces
  • object keys are concatenated with spaces if the value is true.
    • a default option is available, resolving only when all other keys in the object is false. By default, the marker is "_DEFAULT_"
  • falsy values are ignored
classnames("a b", "c", "d"); // => "a b c d"
classnames("a", undefined, false, null, "b"); // => "a b"
classnames("a", ["b", ["c", "d"]], "e", "f"); // => "a b c d e f"

classnames("a", { b: true, c: false, d: 12 }); // => "a b d"
classnames("a", { b: false, c: undefined, d: "_DEFAULT_"}); // => "a d"

// values can be combined and composed
classnames("a b", ["c", ["d", "e"], false], { f: true, g: false }); // => "a b c d e f"

Options

better-classnames can be configured by calling the opt function with a configuration JSON. Output can be configured to be an array instead of a string. Strings with spaces can also be configured to be joined instead of separating. The marker for default options for object processing can also be set.

classnames.opt({ output: "array" }).process("a", "b c"); // => ["a", "b", "c"]
classnames.opt({ joinSpaces: "-" }).process("a b", "c"); // => "a-b c"
classnames
	.opt({ defaultMarker: "NEW-DEFAULT-OPTION"}
    .process({a: false, b: undefined, c: "NEW-DEFAULT-OPTION"}); // => "c"

// shorthand function names can be used
classnames.o({ output: "array", joinSpaces: "-" }).p("a b", "c"); // => ["a-b", "c"]

Overriding

Override a base set of classnames by defining classes that are similar in a likes regular expression list, or explicitly define overrides in the precedence map of regular expressions. Then invoke the override by passing a base classes and dominant classes.

classnames
  .o({ likes: [/^px-\d/, /^py-\d/, /^p-\d/] })
  .override("px-2 py-3 text-gray-500 ", "px-1 py-1"); // => "px-1 py-1 text-gray-500"
classnames
  .o({
    precedences: [
      {
        dominant: [/^p-\d/],
        base: [/^px-\d/, /^py-\d/],
      },
    ],
  })
  .override("px-2 py-3 text-gray-500 ", "p-3"); // => "p-3 text-gray-500"

API

classnames(...parameters) - processes any number of parameters that are strings, objects or lists. classnames.opt(configuration) - defines configuration options. Available options are:

  • output: "string" or "array" - what output is desired, default is "string"
  • joinSpaces: string - if strings with spaces should be joined, if blank then spaces will be separated, it is empty by default.
  • defaultMarker: string - to mark a key in an object as the default option, resolving when all other keys are false. it is "_DEFAULT_" by default.
  • likes: array of regex - define similar items for overriding
  • precedences: array of objects - define explicit precedences for overriding * each object contains "dominant" list of regex and "base" list of regex.

classnames.o(options) - short hand for opt
classnames.o(options).process(...parameters) - process parameters based on selected options
classnames.o(options).p(...parameters) - short hand for process

classnames.o(options).override(base, dominant) - override the base class with the dominant class based on selected options.
classnames.o(options).ovr(base, dominant) - shorthand for override

Example

The following is an example for a button using React using TailwindCSS classnames.

const OldButton = (props) => {
  const getStyling = () => {
    let classList = "px-3 py-2";
    switch (props.variant) {
      case "simple":
        classList += ` bg-transparent border-0`;
        classList += ` ${props.textColor || "text-blue-500"} hover:${
          props.textHoverColor || "text-blue-600"
        }`;
        break;
      case "outline":
        classList += ` bg-transparent border border-blue-500 hover:bg-blue-500 hover:border-transparent rounded`;
        classList += ` ${props.textColor || "text-blue-500"} hover:${
          props.textHoverColor || "text-white"
        }`;
        break;
      default:
        classList += ` bg-blue-500 hover:bg-blue-600 border-blue-500 rounded`;
        classList += ` ${props.textColor || "text-white"} hover:${
          props.textHoverColor || "text-white"
        }`;
        break;
    }
    return classList;
  };
  return <button className={getStyling()}>{props.children}</button>;
};

Note the clunky switch statement and repeated usage of textColor and textHoverColor. Also note how the styling associated with the button is not easily overriden.

const NewButton = (props) => (
  <button
    className={classname
      .o({
        likes: [/^px-\d/, /^py-\d/],
        precedences: [
          {
            base: [/^px-\d/, /^py-\d/],
            dominant: [/^p-\d/],
          },
        ],
      })
      .ovr(
        classname(
          "px-3 py-2",
          {
            "bg-transparent border-0": props.variant === "simple",
            "bg-transparent border border-blue-500 hover:bg-blue-500 hover:border-transparent rounded":
              props.variant === "outline",
            "bg-blue-500 hover:bg-blue-600 border-blue-500 rounded":
              "_DEFAULT_",
          },
          props.textColor || {
            "text-blue-500":
              props.variant === "simple" || props.variant === "outline",
            "text-white": "_DEFAULT",
          },
          props.textHoverColor || {
            "hover:text-blue-500": props.variant === "simple",
            "hover:text-white": "_DEFAULT_",
          }
        ),
        classname(props.className)
      )}
  >
    {props.children}
  </button>
);

By using better-classnames, the styling is now much more declarative with easier to follow logic. The definition of likes and precedences also now allow overriding styles. For example:

const App = () => {
  return (
    <div>
      <NewButton className={"px-5 py-2"}>First Button</NewButton>
      <NewButton className={"p-5"}>Second Button</NewButton>
    </div>
  );
};

The First Button has defined custom x and y padding. Second Button has defined a uniform padding for both x and y. Both overriding the default padding in NewButton.

Contribution

Contributions are welcome, please add new tests as necessary.

License

Licensed under MIT @ Andrew Xia