npm package: @helal_aytah/classname_builder
Description: Headless, zero-dependency TypeScript className builder for the web. Accepts strings, nested arrays and conditional object maps, preserves insertion order and returns a deduplicated space-separated class string. Exports both ESM and CJS builds.
Author: Helal Aytah
Latest version: 1.0.16

# @helal_aytah/classname_builder

[![npm version](https://img.shields.io/npm/v/@helal_aytah/classname_builder.svg?style=flat-square)](https://www.npmjs.com/package/@helal_aytah/classname_builder)
[![npm downloads](https://img.shields.io/npm/dm/@helal_aytah/classname_builder.svg?style=flat-square)](https://www.npmjs.com/package/@helal_aytah/classname_builder)
[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg?style=flat-square)](https://opensource.org/licenses/MIT)
[![CI Status](https://img.shields.io/github/actions/workflow/status/virusoo7/classname_builder/ci.yml?branch=main&style=flat-square)](https://github.com/virusoo7/classname_builder/actions/workflows/ci.yml)
[![Checked with Biome](https://img.shields.io/badge/Checked_with-Biome-60a5fa?style=flat&logo=biome)](https://biomejs.dev)

A headless, zero-dependency TypeScript className builder for the web. It accepts
strings, nested arrays, and conditional object maps, returning a deduplicated,
space-separated class string. It's built to be robust, lightweight, and easy to
integrate into any modern web project.

This utility is perfect for component-based frameworks like **React**,
**Next.js**, **Vue**, or **Svelte**, even **vanilla javascript** where dynamic
and conditional class names are a common requirement.

## Features

- **Tiny & Zero-Dependency**: Adds minimal overhead to your project.
- **Fully Typed**: Written in TypeScript for superior autocompletion and type
  safety.
- **Flexible API**: Effortlessly handles strings, objects, arrays, and any
  nested combination.
- **Smart Deduplication**: Automatically removes duplicate class names.
- **Modern Module Support**: Exports both ESM (`import`) and CJS (`require`)
  builds.
- **Falsy Handling**: Intelligently ignores `null`, `undefined`, and `false` to
  simplify conditional logic.

## Installation

Install the package using your favorite package manager:

```bash
# Using npm
npm install @helal_aytah/classname_builder

# Using yarn
yarn add @helal_aytah/classname_builder

# Using pnpm
pnpm add @helal_aytah/classname_builder
```

---

## Usage Examples

### 1. ES Modules (React, Vue, Svelte, etc.)

This is the most common use case in modern frontend development.

```jsx
import { classNameBuilder } from "@helal_aytah/classname_builder";

const MyButton = ({ isPrimary, isActive, children }) => {
  const classes = classNameBuilder("btn", isPrimary && "btn-primary", {
    active: isActive,
    disabled: !isActive,
  });

  // If isPrimary=true and isActive=true, classes will be: "btn btn-primary active"
  // If isPrimary=false and isActive=false, classes will be: "btn disabled"

  return <button className={classes}>{children}</button>;
};
```

### 2. CommonJS (Node.js / Legacy Build Systems)

For environments that use the `require` syntax, the package works out of the
box.

```javascript
// In a file like `server-component.js` or a script running in Node.js
const { classNameBuilder } = require("@helal_aytah/classname_builder");

// --- Example 1: Basic usage ---
const buttonClasses = classNameBuilder("btn", "btn-large", "btn-primary");
console.log(buttonClasses);
// Output: "btn btn-large btn-primary"

// --- Example 2: Conditional classes ---
const isLoggedIn = false;
const hasError = true;

const userStatusClasses = classNameBuilder({
  "status-online": isLoggedIn,
  "status-offline": !isLoggedIn,
  "has-error": hasError,
});
console.log(userStatusClasses);
// Output: "status-offline has-error"

// --- Example 3: Mixed array ---
const cardClasses = classNameBuilder(
  "card",
  ["card-body", { "shadow-lg": true, rounded: false }],
  isLoggedIn ? "user-card" : null,
);
console.log(cardClasses);
// Output: "card card-body shadow-lg"
```

### 3. Vanilla JS (Directly in the Browser)

You can use the library directly in an HTML file by including a UMD (Universal
Module Definition) build. This is useful for demos, prototyping, or legacy
projects.

#### `index.html`

```html
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Vanilla JS ES Module Example</title>
    <style>
      body {
        font-family: sans-serif;
      }
      .btn {
        padding: 10px 20px;
        border: 1px solid #ccc;
        border-radius: 5px;
        cursor: pointer;
        transition: all 0.2s ease-in-out;
      }
      .btn-primary {
        background-color: #007bff;
        color: white;
        border-color: #007bff;
      }
      .active {
        box-shadow: 0 0 8px rgba(0, 123, 255, 0.8);
        transform: scale(1.05);
      }
      .disabled {
        opacity: 0.5;
        cursor: not-allowed;
      }
    </style>
  </head>
  <body>
    <h1>Vanilla JS ClassName Builder (from CDN)</h1>
    <button id="myButton">Click Me</button>
    <p>Current classes: <code id="class-display"></code></p>

    <!-- 
      The script tag now has type="module".
      This allows us to use the 'import' keyword directly in the browser.
    -->
    <script type="module">
      // Import the function directly from the JSDelivr CDN URL
      import { classNameBuilder } from "https://cdn.jsdelivr.net/npm/@helal_aytah/classname_builder/dist/index.js";

      const button = document.getElementById("myButton");
      const classDisplay = document.getElementById("class-display");

      let isActive = true;
      let isPrimary = true;

      function updateButtonClasses() {
        const classes = classNameBuilder("btn", {
          "btn-primary": isPrimary,
          active: isActive,
          disabled: !isActive, // Add a disabled class when not active
        });

        // Apply the generated classes to the button element
        button.className = classes;

        // Display the current class string
        classDisplay.textContent = `"${classes}"`;
      }

      // Set the initial state of the button
      updateButtonClasses();

      // Add a click event listener to toggle the button's state
      button.addEventListener("click", () => {
        isActive = !isActive; // Toggle the active state
        updateButtonClasses();
      });
    </script>
  </body>
</html>
```

---

## API Reference

The package exports a primary function `classNameBuilder`, a `ClassNameBuilder`
class, and the `TClassNameValue` type.

### `classNameBuilder(...args)`

This is the main function you'll use. It's a convenience wrapper around
`ClassNameBuilder.build`.

- **Parameters**: `...args: TClassNameValue[]` — A spread of values to be
  processed.
- **Returns**: `string | undefined` — A space-separated string of unique class
  names, or `undefined` if no valid classes are generated.

The `TClassNameValue` type can be any of the following:

| Type                           | Description                                                                      | Example                                       |
| ------------------------------ | -------------------------------------------------------------------------------- | --------------------------------------------- |
| `string`                       | A single class or multiple space-separated classes.                              | `'btn btn-primary'`                           |
| `object`                       | A map where keys are class names and truthy values cause the key to be included. | `{ active: true, 'text-red': hasError }`      |
| `Array<TClassNameValue>`       | An array containing any valid `TClassNameValue` type. Arrays can be nested.      | `['base', { conditional: true }, ['nested']]` |
| `null \| undefined \| boolean` | Falsy values that are ignored, allowing for simple inline conditionals.          | `isLoggedIn && 'user-active'`                 |

---

## Development Scripts

This project uses `pnpm` as its package manager and includes several scripts for
development:

- `pnpm dev`: Run Vitest in watch mode for continuous testing.
- `pnpm test`: Run the full test suite once.
- `pnpm lint`: Lint and format the codebase.
- `pnpm bench`: Run benchmark tests.
- `pnpm coverage`: Generate a test coverage report.
- `pnpm build`: Create a production-ready build in the `dist/` folder.
- `pnpm ci`: Run the full continuous integration pipeline (lint, test, build).

## Contributing

Contributions are welcome! If you have a feature request, bug report, or want to
improve the code, please follow these steps:

1. **Fork the repository** on GitHub.
2. **Clone your fork** to your local machine.
3. **Create a new branch** for your changes:
   `git checkout -b my-feature-branch`.
4. **Make your changes** and commit them with a clear message.
5. **Push your branch** to your fork on GitHub.
6. **Open a Pull Request** to the main repository.

Please ensure your code adheres to the existing style and that the `pnpm ci`
command passes successfully.

## License

This project is licensed under the **MIT License**. See the [LICENSE](LICENSE)
file for details.

## Author

- **Helal Aytah** ([@virusoo7](https://www.github.com/virusoo7))

---