https://github.com/askirmas/react-classnaming
Tools to establish CSS classes as an explicit abstraction layer and to handle it as an interface between React and CSSStyleDeclaration
https://github.com/askirmas/react-classnaming
bem classname classnames classnaming css css-classes css-modules declarative material multiple react typescript
Last synced: 10 months ago
JSON representation
Tools to establish CSS classes as an explicit abstraction layer and to handle it as an interface between React and CSSStyleDeclaration
- Host: GitHub
- URL: https://github.com/askirmas/react-classnaming
- Owner: askirmas
- License: mit
- Created: 2021-02-06T14:22:00.000Z (almost 5 years ago)
- Default Branch: main
- Last Pushed: 2021-03-19T15:55:07.000Z (almost 5 years ago)
- Last Synced: 2024-04-25T18:21:35.868Z (almost 2 years ago)
- Topics: bem, classname, classnames, classnaming, css, css-classes, css-modules, declarative, material, multiple, react, typescript
- Language: TypeScript
- Homepage: https://npmjs.com/package/react-classnaming
- Size: 5.59 MB
- Stars: 1
- Watchers: 3
- Forks: 0
- Open Issues: 15
-
Metadata Files:
- Readme: README.md
- License: LICENSE
Awesome Lists containing this project
README
# [react-classnaming](github.com/askirmas/react-classnaming) ![react]()


Tools to establish CSS classes as an explicit [abstraction layer](https://en.wikipedia.org/wiki/Abstraction_layer "a way of hiding the working details of a subsystem, allowing the separation of concerns to facilitate interoperability") and to handle it as an [interface](https://en.wikipedia.org/wiki/Interface_(computing) "a shared boundary across which two or more separate components of a computer system exchange information") between React and [CSSStyleDeclaration](https://developer.mozilla.org/en-US/docs/Web/API/CSSStyleDeclaration "exposes style information and various style-related methods and properties").
[](https://github.com/askirmas/react-classnaming/actions)
[](https://codecov.io/gh/askirmas/react-classnaming)
[](https://sonarcloud.io/dashboard?id=askirmas_react-classnaming)
[](https://codeclimate.com/github/askirmas/react-classnaming/issues)
[](https://scrutinizer-ci.com/g/askirmas/react-classnaming/)
[](https://deepscan.io/dashboard#view=project&tid=13158&pid=16163&bid=340904)
[](https://www.codefactor.io/repository/github/askirmas/react-classnaming)
[](https://david-dm.org/askirmas/react-classnaming)
[](https://www.npmjs.com/package/react-classnaming)
[](https://github.com/askirmas/react-classnaming/blob/main/LICENSE)

## Objectives
1. Use CSS classes as an ontology of front-end project for clean communication between developers and non-tech staff
2. Make CSS classes be an explicit and predictable informational layer
3. Enforce declarative programming paradigm
4. Enforce contract programming (via TypeScript)
## Dev features
1. Enforce single source of truth of class appending – treat as TypeScript-driven dedupe
2. Require strict `boolean` for value of class condition
3. Use IDE type hints as developers' UX for faster issues resolving
4. BEM
5. CSS-modules agnostic
Use package like [`postcss-d-ts`](https://www.npmjs.com/package/postcss-d-ts) to prepare strict declaration of CSS
## Installation and brief overview
```bash
npm install --save react-classnaming
```
```typescript
import {
// Returns function for building `className: string` from conditioned CSS classes with "context" (if was provided) from `props` for using only declared CSS classes
classNaming,
// Similar to classNaming, specifies mapping to component's (i.e. 3rd-party) `className`-related props
classNamesMap,
// Identical function for TS restriction on classes determed in CSS and not used in component
classNamesCheck,
// Works with BEM conditional object
classBeming
} from "react-classnaming"
// Default export is the most simple function
import classNaming from "react-classnaming"
import type {
// Type to declare component's self CSS classes
ClassNamesProperty,
// Type to gather required CSS classes of sub-components
ClassNames,
// `= string | undefined` – type to declare CSS class, global or local
ClassHash,
// `= {className: string}` – useful shortcut
ClassNamed
} from "react-classnaming"
```
## Basic usage
Example of simple CSS classes conditioning – [./\__tests__/readme.spec.tsx:9](./__tests__/readme.spec.tsx#L9-L31)
```tsx
import classNaming from "react-classnaming"
type Props = {
isValid: boolean
readOnly: boolean
}
// isValid = false, readOnly = false
function FormButtons({isValid, readOnly}: Props) {
const cssClasses = classNaming()
const buttonClass = cssClasses({"button": true}) // "button"
return <>
Close
Reset
{ /* className="button_submit button button--disabled" */ }
Submit
>
}
```
As shown, producing function `classNaming` returns a multipurpose object. It can be
- recalled to stack more CSS classes on conditions: `anotherClass = someClass({...})({...})`
- destructed in component's props as `className` singleton: `
`
- used as a string: ` ``${someClass} ${anotherClass}`` `
## Demos
You can find demonstration with all main points in folder [./\__examples__/](./__examples__/), in addition *`*.test.*`* and *`*.spec.*`*. [
- const cssClasses = classNaming()
+ const cssClasses = classNaming()
```

### BEM
It is possible to use BEM as condition query. With explicitly declared CSS classes (i.e. via [`postcss-plugin-d-ts`](https://www.npmjs.com/package/postcss-plugin-d-ts)) TS and IDE will check and hint on available blocks, elements, modifiers and values. [./\__tests__/readme.spec.tsx:165](./__tests__/readme.spec.tsx#L165-L176)
```diff
import {
- classNaming
+ classBeming
} from "react-classnaming"
- const cssClasses = classNaming()
+ const bemClasses = classBeming()
```

## Reference
### type `ClassNamed`
Shortcut for `{className: string}`.
### type `ClassHash`
For serving global and local CSS classes and not moduled CSS stylesheets. CSS-module will be imported as `{[cssClasses: string]: string}`, while for ordinary CSS import `require` returns just empty object `{}`. Their common notation is `{[cssClasses: string]: string | undefined} `, thus `type ClassHash = string | undefined`
### function [`classNaming`](https://github.com/askirmas/react-classnaming/projects/1)
Sets *context* for further type checks in supplying and toggling.
```typescript
classNaming()
classNaming()
classNaming()
classNaming({classnames: require("./some.css")})
classNaming({classnames: module_css, className})
classNaming(this.props)
```
Returns pipe-able (recallable) callback, that also can be destructed as `ClassNamed` or stringifyed
```tsx
const cssClasses = classNaming(...)
const btnClass = cssClasses({ button })
return
```
On TS-level checks that Component's propagated `className` and certain CSS-class are conditioned once
```typescript
const conditionForClass1: boolean = false
const containerClass = classes(true, {class1: conditionForClass1})
const withClass1Twice = containerClass({
class2: true,
//@ts-expect-error – TS tracks that in chain there's only 1 place for class to be conditionally included
class1: otherCondiition
})
const withClassNameTwice = containerClass(
//@ts-expect-error - Same for `className` - it is already added
true
)
```
On `const` hovering will be tooltip with already conditioned classes under this chain
### function `classBeming`
Sets context to returned function for using BEM conditioned CSS classes queries. General argument's shape is
```typescript
// .src/bem.types.ts#L84-L90
type BemInGeneral = {
[base: string]: undefined | boolean | string
| (false|string)[]
| {
[mod: string]: undefined | boolean | string
}
}
```
Output logic: [./src/bem.core.test.ts:13](https://github.com/askirmas/react-classnaming/blob/main/src/bem.core.test.ts#L13-L35)
Featured example: [\./\__tests__/readme.spec.tsx:191](./__tests__/readme.spec.tsx#L191-L221)
---
### Setting options
Default options BEM naming:
- Modifier's and value's separator is a double hyphen `"--"`
- Element's separator is a double underscore `"__"`
It is required to change this options twice, both on JS and TS levels.
- TS: in declaration file like [\./\__recipes\__/global.d.ts](https://github.com/askirmas/react-classnaming/blob/main/__recipes__/global.d.ts) you to add those lines:
```typescript
///
declare namespace ReactClassNaming {
interface BemOptions {
elementDelimiter: "_";
modDelimiter: "-";
}
}
```
And optionally in add to *tsconfig.json*:
```diff
"compilerOptions": {
"types": [
+ "react-classnaming"
]
}
```
- JS: [./\__recipes\__/index.test.ts#L2-L7](https://github.com/askirmas/react-classnaming/blob/main/__recipes__/index.test.ts#L2-L7)
```typescript
import setOptions from "react-classnaming"
setOptions({...})
```
### function [`classNamesMap`](https://github.com/askirmas/react-classnaming/projects/5)
Function to map `classnames` to string props of some (i.e. 3rd-party) component.
```tsx
const { Root } = classnames
const mapping = classNamesMap(classnames)
```
For hint will be used such props of target component that can be assigned to `string`. After calling `mapping` function and setting other properties, usual TypeScript will check for presence of target's required properties and other ordinary for TS things.

### type [`ClassNamesProperty`](https://github.com/askirmas/react-classnaming/projects/3)
Declaration of self Component's `classnames`
```typescript
type MyClasses = ClassNamesProperty<{
class1: ClassHash
class2: ClassHash
}>
```
Can be restricted to use classes only from CSS module. *Note* Currently no IDE's tooltip for hints
```typescript
type MyProps = ClassNamesProperty<
typeof some_module_css,
//@ts-expect-error
{class1: ClassHash, class2: ClassHash, unknownClass: ClassHash}
>
```
### type [`ClassNames`](https://github.com/askirmas/react-classnaming/projects/2)
Collects/gathers required `classnames` from used sub-Components
```typescript
type MyProps = ClassNames // === ClassNamed === {className: string}
type MyProps = ClassNames // {classnames: Props["classnames"]}
type MyProps = ClassNames
type MyProps = ClassNames
```
```tsx
type Props = ClassNames
function Component({className, classnames, "classnames": {Sub1Class}}: Props) {
const classes = classNaming({classnames, className})
return
}
```
### type `ClassNamesFrom`
Obtain `classnames`-object from `props` of functional component, class component or props type
```typescript
ClassNamesFrom;
ClassNamesFrom;
```
### function [`classNamesCheck`](https://github.com/askirmas/react-classnaming/projects/4)
Identical function or returning constant `EMPTY_OBJECT` for keys check of not used classes in components tree
```tsx
import css from "./page.scss"
import App from "./App.tsx"
ReactDOM.render(
```
- Dummies shape
```tsx
;
```
- Checks CSS with defined (not indexed) classes keys. To produce such declaration you can use package [`postcss-plugin-d-ts`](https://www.npmjs.com/package/postcss-plugin-d-ts).
```tsx
import type { ClassNamesFrom } from "react-classnaming/types";
import css_module from "./some.css"; // With class `.never-used {...}`
)} />;
```
## Misc
### Restructuring
#### Using CSS-modules or simulation
It is possible to use CSS modules or simulation without "context" by supplying class-hash value with variable [./\__tests__/readme.spec.tsx:114](./__tests__/readme.spec.tsx#L114-L153)
```diff
// CSS-module, assuming "button" will be replaced with "BTN"
+ import css_module from "./button.module.css"
+ const { button } = css_module
// Module simulation
+ type CssModuleSimulation = { button_submit: ClassHash }
+ const { button_submit } = {} as CssModuleSimulation
type MyClassNames = ClassNamesProperty<
+ typeof css_module &
+ CssModuleSimulation &
{
- button: ClassHash
- button_submit: ClassHash
"button--disabled": ClassHash
}
>
- const buttonClass = cssClasses({ button: true })
+ const buttonClass = cssClasses({ button })
Submit
```
### Versus [`classnames`](https://github.com/JedWatson/classnames#readme) package
See [src/versus-classnames.test.ts](./src/versus-classnames.test.ts)
//TODO Copy here the most significant TS errors
#### No css-modules, just simulation
```tsx
import classnames from "classnames"
// VERSUS
import css from "./some.css"
import classNaming, {classNamesCheck} from "react-classnaming"
import type {ClassNames} from "react-classnaming"
const { class1,
//@ts-expect-error
whatever
} = classNamesCheck<...>(css)
const props: ClassNames<"class2"> = {"classnames": css}
const {class2} = props.classnames
```
#### CSS module
```tsx
import module_css from "./some.module.css" // {"class1": "hash1", "class2": "hash2"}
import classnames_bind from "classnames/bind"
const cx = classnames_bind.bind(module_css)
// No error on redundant CSS-class
// VERSUS
import classNaming from "react-classnaming"
const clases = classNaming({classnames: module_css})
//@ts-expect-error Argument of type '"class3"' is not assignable to parameter
```