Skip to content

Commit ade5bab

Browse files
author
Andrii Kirmas
committed
Merge branch 'main' into release
2 parents 603c932 + 1ee9337 commit ade5bab

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

60 files changed

+12298
-420
lines changed

.github/workflows/main.yml

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -38,8 +38,10 @@ jobs:
3838
${{ runner.os }}-build-
3939
${{ runner.os }}-
4040
- name: install
41-
run: npm ci --prefer-offline --no-audit --silent --quiet --no-progress
42-
41+
run: |
42+
npm ci --prefer-offline --no-audit --silent --quiet --no-progress
43+
cd __recipes__ && npm ci --prefer-offline --no-audit --silent --quiet --no-progress
44+
4345
- name: build
4446
run: npm run build
4547

README.md

Lines changed: 105 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -8,48 +8,63 @@ Tools to establish CSS classes as an explicit [abstraction layer](https://en.wik
88

99
[![dependencies Status](https://status.david-dm.org/gh/askirmas/react-classnaming.svg)](https://david-dm.org/askirmas/react-classnaming) [![version](https://img.shields.io/npm/v/react-classnaming)](https://www.npmjs.com/package/react-classnaming) ![license](https://img.shields.io/npm/l/react-classnaming)
1010

11-
[TOC]
12-
1311
<img src="https://upload.wikimedia.org/wikipedia/commons/a/a7/React-icon.svg" alt="react" height="64px" /><img src="https://raw.githubusercontent.com/microsoft/TypeScript-Website/f407e1ae19e5e990d9901ac8064a32a8cc60edf0/packages/typescriptlang-org/static/branding/ts-logo-128.svg" alt="TypeScript" height="64px" /> <img src="https://upload.wikimedia.org/wikipedia/commons/d/d5/CSS3_logo_and_wordmark.svg" alt="css" height="64px;" />
1412

13+
![](./images/classbeming.gif)
14+
1515
## Objectives
1616

17-
1. Make CSS classes to be explicit and predictable project layer
18-
2. Enforce declaration style programming
19-
3. Enforce contract based development via TypeScript
20-
4. Enforce single source of truth
21-
5. Enforce class-conditions to be strictly `boolean`, not ~~`falsy|truthy`~~
22-
6. Use IDE type hints as developers UX for faster issues resolving
23-
7. CSS-modules agnostic
17+
1. Use CSS classes as an ontology of front-end project for clean communication between developers and non-tech staff
18+
2. Make CSS classes be an explicit and predictable informational layer
19+
3. Enforce declarative programming paradigm
20+
4. Enforce contract programming (via TypeScript)
21+
22+
## Dev features
23+
24+
1. Enforce <u>single source of truth</u> of class appending – treat as TypeScript-driven dedupe
25+
2. Require strict `boolean` for value of class condition
26+
3. Use IDE type hints as developers' UX for faster issues resolving
27+
4. BEM
28+
5. CSS-modules agnostic
2429

2530
Use package like [`postcss-plugin-d-ts`](https://www.npmjs.com/package/postcss-plugin-d-ts) to prepare strict declaration of CSS
2631

27-
## Installation and import
32+
## Installation and brief overview
2833

2934
```bash
3035
npm install --save react-classnaming
3136
```
3237

3338
```typescript
3439
import {
35-
classNaming, // Returns function for building `className: string` from conditioned CSS classes with "context" (if was provided) from `props` for using only declared CSS classes
36-
classNamesMap, // Similar to classNaming, specifies mapping to component's (i.e. 3rd-party) `className`-related props
37-
classNamesCheck // Identical function for TS restriction on classes determed in CSS and not used in component
40+
// Returns function for building `className: string` from conditioned CSS classes with "context" (if was provided) from `props` for using only declared CSS classes
41+
classNaming,
42+
43+
// Similar to classNaming, specifies mapping to component's (i.e. 3rd-party) `className`-related props
44+
classNamesMap,
45+
46+
// Identical function for TS restriction on classes determed in CSS and not used in component
47+
classNamesCheck,
48+
49+
// Works with BEM conditional object
50+
classBeming
3851
} from "react-classnaming"
3952

4053
// Default export is the most frequently used function
4154
import classNaming from "react-classnaming"
4255

43-
// Import module with specific function only
44-
import { classNaming } from "react-classnaming/naming"
45-
import { classNamesCheck } from "react-classnaming/check"
46-
import { classNamesMap } from "react-classnaming/map"
47-
4856
import type {
49-
ClassNamesProperty, // Type to declare component's self CSS classes
50-
ClassNames, // Type to gather required CSS classes of sub-components
51-
ClassHash, // `= string | undefined` – type to declare CSS class, global or local
52-
ClassNamed // `= {className: string}` – useful shortcut
57+
// Type to declare component's self CSS classes
58+
ClassNamesProperty,
59+
60+
// Type to gather required CSS classes of sub-components
61+
ClassNames,
62+
63+
// `= string | undefined` – type to declare CSS class, global or local
64+
ClassHash,
65+
66+
// `= {className: string}` – useful shortcut
67+
ClassNamed
5368
} from "react-classnaming/types"
5469
```
5570

@@ -77,6 +92,7 @@ function FormButtons({isValid, readOnly}: Props) {
7792
<button type="reset" {
7893
...buttonClass({"button--disabled": readOnly}) // className="button"
7994
}>Reset</button>
95+
{ /* className="button_submit button button--disabled" */ }
8096
<button type="submit" className={`button_submit ${
8197
buttonClass({"button--disabled": readOnly || !isValid}) // "button button--disabled"
8298
}`}>Submit</button>
@@ -86,13 +102,13 @@ function FormButtons({isValid, readOnly}: Props) {
86102

87103
As shown, producing function `classNaming` returns a multipurpose object. It can be
88104

89-
- recalled to stack more CSS classes on conditions: `anotherClass = someClass({...})({...})`
90-
- destructed in component's props as `className` singleton: `<div {...someClass}/> <button {...anotherClass}/>`
91-
- used as a string: ` ``${someClass} ${anotherClass}`` `
105+
- <u>recalled</u> to stack more CSS classes on conditions: `anotherClass = someClass({...})({...})`
106+
- <u>destructed in</u> component's <u>props</u> as `className` singleton: `<div {...someClass}/><button {...anotherClass}/>`
107+
- used as a <u>string</u>: ` ``${someClass} ${anotherClass}`` `
92108

93109
## Demos
94110

95-
[<img src="./images/vscode.png" style="width: 50%; float:right;" />](./images/vscode.png) You can find demonstration with all main points in folder [./\__examples__/](./__examples__/), in addition *`*.test.*`* and *`*.spec.*`*.
111+
You can find demonstration with all main points in folder [./\__examples__/](./__examples__/), in addition *`*.test.*`* and *`*.spec.*`*. [<img src="./images/vscode.png" width="50%"/>](./images/vscode.png)
96112

97113
## Getting more
98114

@@ -127,6 +143,22 @@ Only declared CSS classes will be allowed as keys with IDE hint on possibilities
127143

128144
![classnaming_declared](./images/classnaming_declared.gif)
129145

146+
### BEM
147+
148+
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-L186)
149+
150+
```diff
151+
import {
152+
- classNaming
153+
+ classBeming
154+
} from "react-classnaming"
155+
156+
- const cssClasses = classNaming<MyClassNames>()
157+
+ const bemClasses = classBeming<MyClassNames>()
158+
```
159+
160+
![](./images/classbeming.gif)
161+
130162
## Reference
131163

132164
### type `ClassNamed`
@@ -181,6 +213,53 @@ const withClassNameTwice = containerClass(
181213

182214
On `const` hovering will be tooltip with already conditioned classes under this chain
183215

216+
### function `classBeming`
217+
218+
Sets context to returned function for using BEM conditioned CSS classes queries. In general, argument's shape is
219+
220+
```typescript
221+
type BemInGeneral = {
222+
[__Block__]: boolean | __Block_Mod__ | {
223+
[__Element__ | $ /*key for block mods*/]: boolean | __BE_Mod__ | {
224+
[__Mod__]: false | (true | __BE_Mod_Value__ )
225+
}
226+
}
227+
}
228+
```
229+
230+
Table of output logic:
231+
232+
> Tests @ [./src/bem.core.test.ts:13](https://github.com/askirmas/react-classnaming/blob/main/src/bem.core.test.ts#L13-L35)
233+
234+
| Returned `className` | Query argument |
235+
| --------------------------------- | ------------------------------------------------------------ |
236+
| `""` | `{block: false}`<br />`{block: {el: false}}` |
237+
| | |
238+
| `"block"` | `{block: true}`<br />`{block: {$: boolean | {} | {[mod]: false} }}` |
239+
| `"block__el"` | `{block: {el: true | {} | {[mod]: false} }}` |
240+
| | |
241+
| `"block block--mod"` | `{block: "mod"}`<br/>`{block: {$: "mod" | {mod: true} }}` |
242+
| `"block__el block__el--mod"` | `{block: {el: "mod" | {mod: true} }}` |
243+
| | |
244+
| `"block block--mod--val"` | `{block: {$: {mod: "val"}}}` |
245+
| `"block__el block__el--mod--val"` | `{block: {el: {mod: "val"}}}` |
246+
247+
Mixins are deep merge of single possibilities in table
248+
249+
![](./images/classbeming.gif)
250+
251+
---
252+
253+
#### Setting options
254+
255+
Default options BEM naming:
256+
257+
- Element's separator is a double underscore `"__"`
258+
- Modifier's and value's separator is a double hyphen `"--"`
259+
- Key for block modifiers is `"$"`
260+
261+
It is required to change this options twice, both on JS (`setOpts(...)`) and TS `namespace ReactClassNaming { interface BemOptions {...} }`) levels
262+
184263
### function [`classNamesMap`](https://github.com/askirmas/react-classnaming/projects/5)
185264
186265
Function to map `classnames` to string props of some (i.e. 3rd-party) component.

__examples__/App.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import React from "react"
2-
import classNaming, {classNamesMap} from "react-classnaming"
3-
import type { ClassNames, ClassNamesProperty, ClassHash, ClassNamed } from "react-classnaming"
2+
import classNaming, {classNamesMap} from "../src"
3+
import type { ClassNames, ClassNamesProperty, ClassHash, ClassNamed } from "../src"
44

55
import MyComponent from "./MyComponent"
66
import ThirdParty, { ThirdPartyProps } from "./node_modules-third_party_component"

__examples__/MyComponent.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11

22
import React from "react"
3-
import { classNaming } from "react-classnaming"
4-
import type { ClassNamesProperty, ClassHash } from "react-classnaming/types"
3+
import { classNaming } from "../src"
4+
import type { ClassNamesProperty, ClassHash } from "../src"
55

66
type MyComponentProps = ClassNamesProperty<{
77
btn: ClassHash

__examples__/index.spec.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import React from "react"
22
import expectRender from "../expect-to-same-render"
3-
import { classNamesCheck } from "react-classnaming"
3+
import { classNamesCheck } from "../src"
44
import App from "./App"
55

66
// Emulation of import some_module_css from "./some.module.css"

__recipes__/index.test.ts

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
//-/ <reference path="node_modules/react-classnaming/dist" />
2+
import {classBeming, ClassNamed, ClassNamesProperty, setOptions} from "react-classnaming"
3+
import type {ClassHash, ReactClassNaming} from "react-classnaming"
4+
5+
declare module "react-classnaming" {
6+
namespace ReactClassNaming {
7+
interface BemOptions {
8+
elementDelimiter: "_";
9+
modDelimiter: "-";
10+
blockModKey: "&";
11+
}
12+
}
13+
}
14+
15+
setOptions({
16+
elementDelimiter: "_",
17+
modDelimiter: "-",
18+
blockModKey: "&"
19+
})
20+
21+
type CssModule = Record<
22+
"block"|"block-m"
23+
|"block_el"|"block_el-m-X"|"block_el-m-Y",
24+
ClassHash
25+
>
26+
27+
it("go", () => {
28+
const bem = classBeming<ClassNamesProperty<CssModule> & ClassNamed>()
29+
expect(bem(true, {
30+
"block": {
31+
"&": "m",
32+
"el": {"m": "X"}
33+
}
34+
})).toStrictEqual({
35+
className: "block block-m block_el block_el-m-X"
36+
})
37+
})

__recipes__/jest.config.json

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
{
2+
"preset": "ts-jest",
3+
"testEnvironment": "node",
4+
"rootDir": "."
5+
}

0 commit comments

Comments
 (0)