0.3.1 • Published 2 months ago

@auaust/g-class v0.3.1

Weekly downloads
-
License
GPL-3.0-only
Repository
-
Last release
2 months ago
AUAUST LIBRARIES — JavaScript — G-Class

This repository is made public to open the codebase to contributors. When contributing to this repo, you agree to assign your copyrights to AUAUST.

G-Class

G-Class provides a limited set of function to help deal with CSS Modules and CSS classes from JavaScript. Alike the Mercedes-Benz G-Class, it is robust, polyvalent and powerful. Unlike the Mercedes-Benz G-Class, it is lightweight, free, and isn't a car.

Overview

cl(
  "my-class-1",
  false && "my-class-2",
  true && "my-class-3",
  () => "my-class-4",
  [
    "my-class-5",
    {
      "my-class-6": true,
      "my-class-7": false,
    },
  ]
); // => "my-class-1 my-class-3 my-class-4 my-class-5 my-class-6"

Key Features

  • Type Safety – Strongly typed. Even CSS classes.
  • Framework Agnostic – Works with any framework (or no framework at all) as long as you can set classes on your elements by passing a string.
  • No config - The handling of CSS Modules should already be done by your bundler, which means any configuration is already done when G-Class receives values.
  • Easy – WYSIWYG.
  • Robust – Mostly based on native methods, adding a layer of type safety and convenience.
  • No Dependencies – No external dependencies. Just pure JavaScript. (Or TypeScript, if you prefer.)
  • Isomorphic – No usage of special APIs. Only JavaScript.

Installation

yarn add @auaust/g-class

or if you prefer Bun:

bun add @auaust/g-class

Usage

The most simple and common use case is to use the cl() function to generate a string of CSS classes from a set of arguments. G-Class, however, also provides a set of functions that specifically target usage of CSS Modules in a type-safe manner.

cl()(Class List)

To cl() you can pass pretty much anything. All arguments will be converted to a string and concatenated. The following are supported, and will be converted as follows:

  • Any falsy value will be ignored:
    • undefined and null will be ignored.
    • A number will be ignored. (Numbers are not valid CSS classes.)
    • An empty string will be ignored.
  • All booleans will be ignored.
  • A non-empty string will be returned as is.
  • A function will be called and its return value will be re-injected into cl().
  • Each entry of an array will be re-injected into cl().
  • Objects will be looped through, and each key which value is truthy will be included in the output.

Type Safety

The cl() function expects inputs of type GClass.Classable. If you happen to pass a value that's not accepted by the type system, which is unlikely if you use the function as intended, you can use foo as GClass.Classable. There's no risk of runtime errors with cl() even if you pass invalid values. They'll just be ignored.

gc()(Global Classes)

The advantage of using CSS Modules is that you can easily type them with existing packages such as typed-css-modules. This means you can import a CSS file in you project and be sure to only apply valid CSS classes to your elements. A rather annoying downside of using CSS Modules, tho, is the need to constantly import a bunch of CSS files and then use them in complex template literals. The cl() function was the first step to solve this issue, as passing a few arguments to a function is much easier and safer than building a template literal. We still end up with a bunch of imports which are also often globally-targetted CSS Modules (especially when using utility-first design patterns).

The gc() function solves this issue. You can register the global CSS Modules once, then use them in your code by only importing the gc() function:

import { gc } from "@auaust/g-class";

function Component() {
  return (
    <div
      class={gc(
        "text:black",
        "text:alignCenter",
        "color:bgBlue"
        // ...and any other CSS class you've registered
      )}
    >
      {" "}
      G-Class is awesome!{" "}
    </div>
  );
}

Registering Global CSS Modules

By default, no class is registered thus no class can be used. The registerModule function will register a CSS Module and make it available to the gc() function. You can register as many CSS Modules as you want. You can also register multiple CSS Modules under a single namespace, which can help you split your CSS Modules into multiple files. If you register multiple CSS Modules under the same namespace and they contain the same CSS class, the last one registered will be used.

The first argument of registerModule is the namespace, and the second argument is the CSS Module itself. A CSS Module is an object where the keys are the CSS classes that map to a unique string, generated by your CSS bundler.

/* styles.module.css */

.color-black {
  color: black;
}

.quote {
  color: darkgray;
  text-align: center;
  font-style: italic;
}
.quote::before {
  content: "“";
}
.quote::after {
  content: "”";
}
import { registerModule, gc } from "@auaust/g-class";
import styles from "./styles.module.css";

registerModule("text", styles);

gc("text:colorBlack"); // => "_style_color-black_1jac3" (or whatever your CSS bundler generates)
gc("text:quote"); // => "_style_quote_5b1h3"

gc("text:colorBlack", "text:quote"); // => "_style_color-black_1jac3 _style_quote_5b1h3"

gc("invalid:class"); // => "", will not throw an error but will return an empty string silently

Even thought there's likely no reason to do so, note you can remove a registered CSS Module by calling dropModule(namespace).

Class names

The actual CSS Modules implementation must be done by your bundler. If you're using Vite, they're supported out of the box. This means whether you should use gc("test:my-class") or gc("test:myClass") depends on your bundler and external configuration.

G-Class is transparent about this and will not modify the class names you pass to gc().

Type Safety

Out of the box, gc() will accept any input that matches ${string}:${string}. This means any namespace and any class name will be accepted as long as the general string format is respected. To provide great type safety, you need to extend the RegisteredCSSModules interface with your own CSS Modules. Be aware that as soon as a single CSS Module is registered, the gc() function will become strongly typed and any unregistered class you pass to it will be highlighted by your IDE.

To extend the interface, you can either pass the type of a CSS Module or true, which will allow any class name to be passed under the namespace.

import { registerModule } from "@auaust/g-class";
import textStyles from "./text.module.css";
import colorStyles from "./color.module.css";
import gridStyles from "./grid.module.css";

declare module "@auaust/g-class" {
  interface RegisteredCSSModules {
    text: typeof textStyles;
    color: true;
  }
}

registerModule("text", textStyles);
registerModule("color", colorStyles);
registerModule("grid", gridStyles);

gc("text:colorBlack"); // Will be allowed AND will be hinted by your IDE
gc("text:invalidClass"); // Will be highlighted as an error by your IDE
gc("color:invalidClass"); // Will be allowed since any class name is allowed under the "color" namespace
gc("grid:validClass"); // Will be highlighted as an error by your IDE as the interface was not extended for the "grid" namespace

// If, however, you used the same code without extending the interface, each entry would be accepted as they respect the `${string}:${string}` format.

Concrete Example

Since the gc() function returns a string, you can easily include it in the cl() function too. If you only need to use global classes, you can safely only use the gc() function as it provides the same runtime safety as the cl() function.

import { cl, gc } from "@auaust/g-class";
import styles from "./my-component-styles.module.css";

function Component() {
  return (
    <div
      clas={cl(
        "my-global-class",
        active() && "my-active-class", // Would work in a reactive context, if your framework supports it
        styles.myComponentClass,
        gc("text:colorBlack", "text:quote")
      )}
    >
      <p class={gc("text:mono")}> Hello World! </p>
    </div>
  );
}

Related

The main function, cl(), is widely inspired by clsx. If you're looking for a more minimalistic solution, don't need functions to be called and don't use the globally registered CSS Modules, you should definitely check it out.

Sponsor

This library is a project by us, AUAUST. We sponsor ourselves!

0.3.1

2 months ago

0.3.0

3 months ago

0.2.0

4 months ago

0.1.2

5 months ago

0.1.1

5 months ago

0.1.0

5 months ago

0.0.0

5 months ago