npm package: @ibnlanre/recss
Description: ReCSS is a library that facilitates writing atomic classes for UI elements
Author: Ridwan Olanrewaju
Latest version: 3.2.0

# ReCSS

[![TypeScript](https://img.shields.io/badge/%3C%2F%3E-TypeScript-%230074c1.svg)](http://www.typescriptlang.org/)
[![code style: prettier](https://img.shields.io/badge/code_style-prettier-f8bc45.svg)](https://github.com/prettier/prettier)
![version](https://img.shields.io/badge/version-3.2.0-orange)
[![Twitter](https://img.shields.io/twitter/follow/ibnlanre?style=social&label=Follow)](https://twitter.com/intent/follow?screen_name=ibnlanre)

---

ReCSS is a library that facilitates writing atomic classes for UI elements. From factory, ReCSS comes bundled with a pre-built [tailwindcss](https://tailwindcss.com/) grammar, yet, supports other CSS frameworks ([tachyons](https://tachyons.io/), [bootstrap](https://getbootstrap.com/), [funcssion](https://funcssion.com/), [basscss](https://basscss.com/), [skeleton](http://getskeleton.com/), [quantum](https://github.com/grabr/quantum-css) etc.) including custom ones; it does this by filtering the atomic styles included within provided stylesheets. As of v3.1.0, we recommend using [Atomize](https://www.npmjs.com/package/@ibnlanre/atomize) to perform that function.

---

## Installation

```bash
npm i @ibnlanre/recss
```

## API

### Browser

Due to ease of bundling, ReCSS imports light and doesn't utilize code that isn't browser compatible.

```javascript
<script src="https://unpkg.com/@ibnlanre/recss/index.js"></script>
```

### Import

ReCSS has only one export and it is the default. There should be no need to append `.default` when using the CommonJS syntax, but it there's an error being thrown, use `const { default as ReCSS } = require("@ibnlanre/recss");`. With TypeScript's `esModuleInterop` set to true in `tsconfig.json`, you can also blend the two syntaxes together.

```javascript
// NodeJS Require
const ReCSS = require("@ibnlanre/recss");
// ES6 Import
import ReCSS from "@ibnlanre/recss";
// With esModuleInterop
import ReCSS = require("@ibnlanre/recss");
```

### Comments

```markdown
//→ what the following returns
//? if the precedent is optional
//# denotes the default value
//: means refers | is equivalent to
//! note | warning sign
```

### Overview

This illustration is not entirely necessary as ReCSS is well-typed

```markdown
var recss = new ReCSS( theme?: (object | string)[] | {}, options? );
var { re, css } = new ReCSS( theme?, options?: {} );
```

### Getters and Setters

These are the options that ReCSS gives you to define the resulting output. Note that they can also be temporarily set using `re({ options? })`. By _temporary_, `re` is expected to be used with `css`; that way, the options set can be used immediately. But it is not a requirement, since the `options` object is reset just before `css` returns.

```markdown
recss.options = {
computable: boolean; //# true
dissolve: boolean; //# true
filter: string | boolean; //# false
grammar: object //# tailwindcss
matcher: regex //# undefined
objectMode: boolean //# false
orderClass: boolean //# true
orderProps: boolean //# false
reverseOrder: boolean //# false
silent: boolean //# true
}
```

The `theme` serves as a getter and setter for themes assigned at initialization. This allows for it for be re-assigned or extended. To re-assign, use an Object i.e. `{ themes? }` and to extend, use an Array i.e. `[ themes ]`; it is called extension because it works a little bit different from `Object.assign()` — it extends, not overrides. So, to override existing values, use `{ ...recss.theme, new_themes }` instead.

```markdown
recss.theme: { } :→ Object.assign() | [] → for extension
```

### Getters Only

The `store` is a object map of `id` values to their evaluated results. The `id` values only get added to the store if they were passed in when calling `css()`. This is important to note because it serves as a reference for the object returned, which serves as a reference for future amendments later on in the code.

```markdown
recss.store: this keeps track of the reference store
```

`grammar` on the other hand, is an object that returns `TailwindCSS` class-attribute pairs, else the object assigned to `options.grammar`. Note that it cannot be re-assigned directly, and contains a mapping that is relatively lengthy printing to `stdout`. Use `util.inspect` to assess.

```markdown
recss.grammar: built from the stylesheet provided
```

### Grammar

```javascript
// import tailwind from "./tailwind.js"; | use @ibnlanre/atomize

recss.options = {
  // re-assignments override the initial | default values set
  "grammar": atomize({ //?  it is a static method
    file: "../tests/tailwind.css", // the relative css file path
    sep: ":",  //? the separator in atomic classes e.g. sm:px-1
    path: __dirname, //? for easier resolution
    save: false //? if the file should be saved to the system or not
    dest: "./" //? location to save the resulting file to, if { save: true }
  }), //! it returns the grammar object //: "grammar": tailwind,
  "matcher": /[\w-:/]+/, //? for matching atomic class in stylesheet
};
```

### Initialization

ReCSS introduces a new syntax for writing objects, based on the [CSS Object Model](https://www.w3.org/TR/cssom-1/). It is not an implementation, rather a relatively new way to write JS objects as strins called `Read-once object`, named after the [Read-once function](https://en.wikipedia.org/wiki/Read-once_function). Note that ReCSS still supports writing objects the [JS](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object) way.

```javascript
{ margin: { bottom: "mb-3", left: "ml-2", } } //: becomes "margin.bottom: mb-3; margin.left: ml-2"
```

When initializing, `new ReCSS()` can take either an Array or an Object, that would determine the theme. As an array, the values are blended into one — no overrides. To override, use the `spread` operator i.e. `{ ...theme1, ...theme2 }`. It exposes two instance methods `re` and `css`. Use with caution! :)

```javascript
const recss = new ReCSS([
  "margin.bottom: mb-3; margin.left: ml-2",
  "colors.pink: bg-pink-500; colors.purple: bg-purple-500",
  "margin.top: mt-3; margin.right: mr-4;"
  { button: "px-2 py-3" },
]);

const { re, css } = recss;
```

### Usage

All inputs to `css` are checked in the theme for existence. It takes strings (as a collection of classes or keys) and objects (either in `JSX` format or as valid CSS attributes) i.e. `marginLeft === "margin-left"`. When an `id` is introduced, it serves as a datum for the rest of the input; that is, it is evaluated first. And other values can override it. Note that, although `css()` doesn't understand `Read-once Objects`, it does introduce its own syntax for targeting values: **strings with "." represent nested values** stored in the theme.

```javascript
//→ { id: 'pink_btn', className: 'px-4 py-4 bg-pink-500 ml-2' }
const pinkButton = css(
  "button px-4",
  { marginLeft: "margin.left" },
  { id: "pink_btn", paddingY: "py-4" },
  "colors.pink"
);
```

ReCSS filters in two ways, as a boolean and as a string. As a boolean, set to `true`, ReCSS filters out properties that are not **".className"**, returning an object with `{ className?: string }`. Whereas, when `options.filter` is set to a string, it returns a value with a key equal to the passed string. So in `React`, we can have `<div className={css("some classes")} ></div>`

```javascript
recss.options = { filter: true };
```

Although `re` can be used to temporarily amend `recss.options`, it can also be used to redact "id" values saved to the store. This works by passing the "id" name as a argument to `re`, which in turn returns an object `{ re: this.re, css() }`. Note that the `css` function returned is not the same as `recss.css()`.

```javascript
//→ { className: 'px-6 py-3 bg-pink-500 ml-2' } // id filtered
const pinkButton2 = re({ filter: true }).re("pink_btn")
  .css`pink_btn button px-6`;
console.log(pinkButton === pinkButton2); // true
```

## React

Although ReCSS is written for React, it doesn't get its name from it. That note is best left to the Creator.

```javascript
import React, { useState } from "react";
import { render } from "react-dom";

recss.theme = ["gray_btn: border-gray-700"];
const [isActive, setIsActive] = useState(false);
```

ReCSS comes with some cool features that makes it perfect for React. Some of which are:

- MYOB: ReCSS minds its own business. Its core functionality is to evaluate atomic classes and that's exactly what it does, but with a perk; it allows other unrelated properties to be passed in, filtering out those that are needed and spitting out the rest without modifying them. Note that in the following code, `onClick()` can as well be outside `css()` and still function accurately.

- Auto-Prop: if a key with the name is passed in at the top-level of any object, its values are instantly blended without the need for destructuring. This behaviour can be disabled with `{ autoprop? }` set to `false`.

```javascript
const GrayButton = (props) => (
  <div
    {...css("button gray_btn", {
      backgroundColor: isActive ? "bg-gray-300" : "",
      onClick: () => setIsActive(!isActive),
      props, //! it's best practice to put props last
    })}
  />
);
```

- Baseline: ReCSS allows setting base classes which are protected from getting replaced if evaluated alongside other inputs. As with the example below, `css()` also accept a `{ rebase? }` property, which gets added to the string input and is affected in the evaluation.

```javascript
//→ <div class="px-5 py-1 border-gray-700 p-3" onclick="..." ></div>
render(<GrayButton paddingY="py-1" base="p-3" rebase="px-5" />, root);
//! { base? } values are added to className blindly, without vetting
```

- Error Mode: When `css(" ")` is called with an empty string or an error occurs, it returns `{ className: undefined }`. To examine the error, use `re({ silent: false })`. And in case if you're not satisfied with how a result is evaluated so, and you're curious to find out why, use `{ objectMode: true }`.

- Templating: ReCSS also supports writing styles with template literals ( css`style` )

```javascript
//→ <div id="grey_btn" onclick="..."></div>
render(<GrayButton id="grey_btn" {...css``} />, root);
```

- Object Dissolution: Objects can either be dissolved into classes or props

```javascript
const { css } = new ReCSS({
  image: { src: "./path/to/ }
}, { dissolve: true });

//→ <img alt="some-image" class="w-full" src="./path/to/img.png" />
render(<img {...css`image w-full`} />, root);
```

- Computed Properties: `theme` properties can be used methodically to calculate the resulting class. To reference a method within another, use `this.[method_name]`. ReCSS introduces another manner in which the function can be called from a string `function_name[arg1, arg2]` - the comma is not necessary. The reason why it is a *square bracket*, rather than parentheses is because classes with *square brackets* are not atomic. And also because, [Atomic CSS](https://acss.io/) classes use parentheses.

```javascript
const { css } = new ReCSS({
    use: (...values, x) => values.map((e) => x + e).join(" "),
    sm: (...val) => this.use(...val, "sm:")
    lg: (...values) => values.map((e) => "lg:" + e).join(" "),
  }, { computable: true });

//→ <div class="px-4 pt-4 pb-4 sm:px-6 sm:pb-6 lg:px-4 lg:pb-4 ></div>
render(<div {...css("px-4 pt-4 pb-4", "sm[px-6 pb-6] lg[px-4, pb-4]")} />, root);
```