diff --git a/README.md b/README.md
index 35fc5e8..aca1ae7 100644
--- a/README.md
+++ b/README.md
@@ -24,7 +24,8 @@ Tools to establish CSS classes as an explicit [abstraction layer](https://en.wik
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. CSS-modules agnostic
+4. BEM
+5. CSS-modules agnostic
Use package like [`postcss-plugin-d-ts`](https://www.npmjs.com/package/postcss-plugin-d-ts) to prepare strict declaration of CSS
@@ -43,17 +44,15 @@ import {
classNamesMap,
// Identical function for TS restriction on classes determed in CSS and not used in component
- classNamesCheck
+ classNamesCheck,
+
+ // Works with BEM conditional object
+ classBeming
} from "react-classnaming"
// Default export is the most frequently used function
import classNaming from "react-classnaming"
-// Import module with specific function only
-import { classNaming } from "react-classnaming/naming"
-import { classNamesCheck } from "react-classnaming/check"
-import { classNamesMap } from "react-classnaming/map"
-
import type {
// Type to declare component's self CSS classes
ClassNamesProperty,
@@ -144,6 +143,22 @@ Only declared CSS classes will be allowed as keys with IDE hint on possibilities
![classnaming_declared](./images/classnaming_declared.gif)
+### 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-L186)
+
+```diff
+import {
+- classNaming
++ classBeming
+} from "react-classnaming"
+
+- const cssClasses = classNaming()
++ const bemClasses = classBeming()
+```
+
+![](./images/classbeming.gif)
+
## Reference
### type `ClassNamed`
@@ -198,6 +213,53 @@ const withClassNameTwice = containerClass(
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. In general, argument's shape is
+
+```typescript
+type BemInGeneral = {
+ [__Block__]: boolean | __Block_Mod__ | {
+ [__Element__ | $ /*key for block mods*/]: boolean | __BE_Mod__ | {
+ [__Mod__]: false | (true | __BE_Mod_Value__ )
+ }
+ }
+}
+```
+
+Table of output logic:
+
+> Tests @ [./src/bem.core.test.ts:13](https://github.com/askirmas/react-classnaming/blob/main/src/bem.core.test.ts#L13-L35)
+
+| Returned `className` | Query argument |
+| --------------------------------- | ------------------------------------------------------------ |
+| `""` | `{block: false}`
`{block: {el: false}}` |
+| | |
+| `"block"` | `{block: true}`
`{block: {$: boolean | {} | {[mod]: false} }}` |
+| `"block__el"` | `{block: {el: true | {} | {[mod]: false} }}` |
+| | |
+| `"block block--mod"` | `{block: "mod"}`
`{block: {$: "mod" | {mod: true} }}` |
+| `"block__el block__el--mod"` | `{block: {el: "mod" | {mod: true} }}` |
+| | |
+| `"block block--mod--val"` | `{block: {$: {mod: "val"}}}` |
+| `"block__el block__el--mod--val"` | `{block: {el: {mod: "val"}}}` |
+
+Mixins are deep merge of single possibilities in table
+
+![](./images/classbeming.gif)
+
+---
+
+#### Setting options
+
+Default options BEM naming:
+
+- Element's separator is a double underscore `"__"`
+- Modifier's and value's separator is a double hyphen `"--"`
+- Key for block modifiers is `"$"`
+
+It is required to change this options twice, both on JS (`setOpts(...)`) and TS `namespace ReactClassNaming { interface BemOptions {...} }`) levels
+
### function [`classNamesMap`](https://github.com/askirmas/react-classnaming/projects/5)
Function to map `classnames` to string props of some (i.e. 3rd-party) component.
diff --git a/__tests__/readme.spec.tsx b/__tests__/readme.spec.tsx
index 632ac42..d80eb50 100644
--- a/__tests__/readme.spec.tsx
+++ b/__tests__/readme.spec.tsx
@@ -1,6 +1,6 @@
import React from "react"
import expectRender from "../expect-to-same-render"
-import classNaming from "../src"
+import classNaming, { classBeming, ClassNamed } from "../src"
import type {ClassHash, ClassNamesProperty} from "../src"
// import css_module from "./button.module.css"
const css_module = {button: "BTN"}
@@ -160,3 +160,29 @@ it("Using ClassHash", () => {
>)
})
+
+it("bem", () => {
+ type MyClassNames = ClassNamed & ClassNamesProperty<{
+ form__item: ClassHash
+ button: ClassHash
+ "button--status--warning": ClassHash
+ "button--status--danger": ClassHash
+ button__icon: ClassHash
+ "button__icon--hover": ClassHash
+ "button__icon--focus": ClassHash
+ }>
+ const props = {className: "${props.className}"} as MyClassNames
+
+ const bem = classBeming(props)
+ expectRender(
+
+ ).toSame(
+
+ )
+})
diff --git a/images/classbeming.gif b/images/classbeming.gif
new file mode 100644
index 0000000..cc320d7
Binary files /dev/null and b/images/classbeming.gif differ
diff --git a/package.json b/package.json
index 4613512..5ac08b1 100644
--- a/package.json
+++ b/package.json
@@ -28,8 +28,9 @@
"typescript",
"declarative",
"css-classes",
- "css",
"react",
+ "bem",
+ "css",
"classname",
"css-modules",
"classnames",
diff --git a/src/bem.core.test.ts b/src/bem.core.test.ts
index e837608..9d8de77 100644
--- a/src/bem.core.test.ts
+++ b/src/bem.core.test.ts
@@ -1,4 +1,4 @@
-import type {BemAbsraction} from "./bem.types"
+import type {BemInGeneral} from "./bem.types"
import type {BemOptions} from "./bem.core";
import {
bem2arr,
@@ -9,7 +9,7 @@ import {
describe(bem2arr.name, () => {
describe("singletons", () => {
const mod = undefined
- const suites: Record = {
+ const suites: Record = {
"block singleton": [
[{block: false }, ""],
[{block: true }, "block"],
diff --git a/src/bem.core.ts b/src/bem.core.ts
index 0c5a238..558410c 100644
--- a/src/bem.core.ts
+++ b/src/bem.core.ts
@@ -1,4 +1,4 @@
-import type { BemAbsraction } from "./bem.types"
+import type { BemInGeneral } from "./bem.types"
let elementDelimiter = "__"
, modDelimiter = "--"
@@ -16,7 +16,7 @@ export {
getOptions
}
-function bem2arr(query: BemAbsraction) {
+function bem2arr(query: BemInGeneral) {
const $return: string[] = []
for (const block in query) {
diff --git a/src/bem.ts b/src/bem.ts
index 589f5d9..7194db0 100644
--- a/src/bem.ts
+++ b/src/bem.ts
@@ -1,5 +1,5 @@
import type { CssModule } from "./definitions.types";
-import type { ClassBeming, BemAbsraction } from "./bem.types";
+import type { ClassBeming, BemInGeneral } from "./bem.types";
import { bem2arr } from "./bem.core";
import { joinWithLead, picker, wrapper } from "./core"
import { EMPTY_OBJECT } from "./consts.json"
@@ -8,6 +8,15 @@ export {
classBeming
}
+/** Set context
+ * @example
+ * ```typescript
+ * const bem = classBeming({classnames: require("./some.css"), className?})
+ * const bem = classBeming(this.props)
+ * const bem = classBeming()
+ * const bem = classBeming()
+ * ```
+ */
function classBeming<
Ctx extends {classnames: Source, className?: string},
Source extends CssModule = Ctx["classnames"],
@@ -31,8 +40,8 @@ function bem<
className?: string,
classnames?: Source,
},
- arg0?: boolean | BemAbsraction,
- arg1?: BemAbsraction
+ arg0?: boolean | BemInGeneral,
+ arg1?: BemInGeneral
) {
const source = typeof arg0 === "object" ? arg0 : arg1
, debemed = source && bem2arr(source)
diff --git a/src/bem.types.ts b/src/bem.types.ts
index 307e5f0..1d2917f 100644
--- a/src/bem.types.ts
+++ b/src/bem.types.ts
@@ -4,7 +4,7 @@ import type {
Strip,
PartDeep,
Extends,
- // PartDeep
+ Ever0
} from "./ts-swiss.types"
import type { ClassNamed } from "./main.types"
import type {ReactClassNaming} from "."
@@ -12,9 +12,53 @@ import type {ReactClassNaming} from "."
export type ClassBeming<
ClassNames extends CssModule,
> =
+/**
+ * Makes `string`-className from conditioned BEM query based on supplied CSS classes.
+ * Destructed to singleton `{className: string}`, stringifyable object
+ * @returns
+ * ```typescript
+ * // ""
+ * {block: false}
+ * {block: {el: false}}
+ * // "block"
+ * {block: true}
+ * {block: {$: boolean | {} | {[mod]: false} }}
+ * // "block__el"
+ * {block: {el: true | {} | {[mod]: false} }}
+ * // "block block--mod"
+ * {block: "mod"}
+ * {block: {$: "mod" | {mod: true} }}
+ * // "block__el block__el--mod"
+ * {block: {el: "mod" | {mod: true} }}
+ * // "block block--mod--val"
+ * {block: {$: {mod: "val"}}}
+ * // "block__el block__el--mod--val"
+ * {block: {el: {mod: "val"}}}
+ * ```
+ * @example
+ * ```typescript
+ * bem(true) // `${props.className}`
+ * bem({button: true}) // "button"
+ * bem({button: {icon: true}}) // "button__icon"
+ * bem({button: "disabled"}) // "button button--disabled"
+ * bem({button: {icon: {size: "big"}}}) // "button__icon button__icon--size--big"
+ * bem(true, {
+ * form: {item: true},
+ * button: {
+ * $: {status: "danger"},
+ * icon: "hover"
+ * }
+ * }) // `${props.className} form__item button button--status--danger button__icon button__icon--hover`
+ * ```
+ * @example
+ * ```typescript
+ * ;
+ *
+ * ```
+*/
<
Q1 extends undefined | boolean | BemQuery,
- // Q2 extends BemQuery,
+ // Q2 extends BemQuery will be needed for #31
>(
arg0?: Q1 extends undefined | boolean ? Q1 : Subest, Q1> ,
arg1?: Q1 extends undefined | boolean ? BemQuery : never
@@ -31,7 +75,7 @@ export type BemQuery<
bModKey extends string = "blockModKey" extends keyof ReactClassNaming.BemOptions
? ReactClassNaming.BemOptions["blockModKey"]
: ReactClassNaming.BemOptions["$default"]["blockModKey"],
-> = string extends classes ? BemAbsraction : PartDeep<{
+> = string extends classes ? BemInGeneral : PartDeep<{
[b in Strip, delE>]: boolean
| Exclude, `${string}${delM}${string}`>
| (
@@ -41,11 +85,15 @@ export type BemQuery<
| Exclude, `${string}${delM}${string}`>
| (
{[m in Strip, delM>]:
- classes extends `${b}${
- e extends bModKey ? "" : `${delE}${e}`
- }${delM}${m}${delM}${infer V}`
- ? false | V
- : boolean
+ false | (
+ Ever0<
+ classes extends `${b}${
+ e extends bModKey ? "" : `${delE}${e}`
+ }${delM}${m}${delM}${infer V}`
+ ? V : never,
+ true
+ >
+ )
}
)
}
@@ -88,7 +136,7 @@ type MVs<
e extends bModKey ? "" : `${delE}${e}`
}${delM}${infer MV}` ? MV : never
-export type BemAbsraction = {
+export type BemInGeneral = {
[block: string]: undefined | boolean | string | {
[el: string]: undefined | boolean | string | {
[mod: string]: undefined | boolean | string
diff --git a/src/naming.ts b/src/naming.ts
index 3be98c9..67bad2f 100644
--- a/src/naming.ts
+++ b/src/naming.ts
@@ -22,8 +22,8 @@ export { classNaming }
/** Set context
* @example
* ```typescript
- * const classes = classNaming(this.props)
* const classes = classNaming({classnames: require("./some.css"), className?})
+ * const classes = classNaming(this.props)
* const classes = classNaming()
* const classes = classNaming()
* ```
diff --git a/src/naming.types.ts b/src/naming.types.ts
index 6ae6838..188a076 100644
--- a/src/naming.types.ts
+++ b/src/naming.types.ts
@@ -23,12 +23,12 @@ import type {
// Making as interface make ts-errors much worth
export type ClassNamingFn