├── .commitlintrc.json ├── .config └── jest │ ├── jest.config.js │ └── jest.setup.js ├── .editorconfig ├── .eslintignore ├── .eslintrc ├── .github └── workflows │ └── ci.yml ├── .gitignore ├── .huskyrc ├── .lintstagedrc ├── .npmrc ├── .prettierrc ├── CONTRIBUTING.md ├── LICENSE.md ├── README.md ├── environment.d.ts ├── lerna.json ├── package.json ├── packages ├── classname │ ├── .gitignore │ ├── CHANGELOG.md │ ├── README.md │ ├── classname.ts │ ├── index.cjs │ ├── package.json │ ├── test │ │ └── classname.test.ts │ └── tsconfig.json ├── classnames │ ├── .gitignore │ ├── CHANGELOG.md │ ├── README.md │ ├── classnames.ts │ ├── index.cjs │ ├── package.json │ ├── test │ │ └── classnames.test.ts │ └── tsconfig.json ├── core │ ├── .gitignore │ ├── CHANGELOG.md │ ├── README.md │ ├── core.ts │ ├── index.cjs │ ├── package.json │ ├── test │ │ ├── compose.test.tsx │ │ └── withBemMod.test.tsx │ └── tsconfig.json ├── di │ ├── .gitignore │ ├── CHANGELOG.md │ ├── README.md │ ├── di.tsx │ ├── index.cjs │ ├── package-lock.json │ ├── package.json │ ├── test │ │ └── di.test.tsx │ └── tsconfig.json ├── eslint-plugin │ ├── CHANGELOG.md │ ├── README.md │ ├── docs │ │ └── rules │ │ │ ├── no-classname-runtime.md │ │ │ └── whitelist-levels-imports.md │ ├── index.js │ ├── lib │ │ └── rules │ │ │ ├── no-classname-runtime.js │ │ │ └── whitelist-levels-imports.js │ ├── package.json │ └── tests │ │ └── lib │ │ └── rules │ │ ├── no-classname-runtime.js │ │ └── whitelist-levels-imports.js ├── pack │ ├── .eslintrc │ ├── .gitignore │ ├── CHANGELOG.md │ ├── README.md │ ├── bin │ │ └── pack │ ├── package-lock.json │ ├── package.json │ ├── src │ │ ├── build.ts │ │ ├── cli │ │ │ └── build.ts │ │ ├── debug.ts │ │ ├── interfaces.ts │ │ ├── loadConfig.ts │ │ ├── measurePerf.ts │ │ ├── plugins │ │ │ ├── BabelTypeScriptPlugin.ts │ │ │ ├── CleanUpPlugin.ts │ │ │ ├── CopyAssetsPlugin.ts │ │ │ ├── CssPlugin.ts │ │ │ ├── PackageJsonPlugin.ts │ │ │ ├── TypeScriptPlugin.ts │ │ │ └── TypeScriptResolvePlugin.ts │ │ ├── progress.ts │ │ ├── stdout.ts │ │ └── wrapToPromise.ts │ └── tsconfig.json └── webpack-exp-plugin │ ├── CHANGELOG.md │ ├── Readme.md │ ├── index.js │ ├── package-lock.json │ ├── package.json │ ├── replacer.js │ └── replacer.test.js ├── scripts └── rollup │ ├── build.js │ └── terser.config.js ├── tsconfig.json └── website ├── .gitignore ├── .npmrc ├── README.md ├── docs ├── api │ ├── classname │ │ ├── ClassNameFormatter.md │ │ ├── ClassNameInitilizer.md │ │ ├── Preset.md │ │ ├── api.md │ │ ├── cn.md │ │ └── withNaming.md │ ├── classnames │ │ ├── api.md │ │ └── classnames.md │ ├── core │ │ ├── compose.md │ │ └── core.md │ └── di │ │ └── api.md ├── guides │ ├── experiments.md │ ├── lazy.md │ ├── naming.md │ ├── start.md │ └── structure.md └── introduction │ └── quick-start.md ├── docusaurus.config.js ├── package.json ├── sidebars.js ├── src ├── css │ └── custom.css └── pages │ ├── index.js │ └── styles.module.css └── static └── img ├── favicon.ico └── logo.svg /.commitlintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": [ 3 | "@commitlint/config-conventional" 4 | ] 5 | } 6 | -------------------------------------------------------------------------------- /.config/jest/jest.config.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const { resolve } = require('path') 4 | 5 | /** 6 | * @type {import('@jest/types').Config.InitialOptions} 7 | */ 8 | module.exports = { 9 | rootDir: process.cwd(), 10 | setupFiles: [resolve(__dirname, 'jest.setup.js')], 11 | preset: 'ts-jest', 12 | testMatch: ['/**/*.test.{ts,tsx,js}'], 13 | collectCoverageFrom: ['**/*.{ts,tsx}', '!**/*.d.ts', '!**/*.test.{ts,tsx}'], 14 | testEnvironment: 'jsdom', 15 | } 16 | -------------------------------------------------------------------------------- /.config/jest/jest.setup.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | global.requestAnimationFrame = (callback) => { 4 | setTimeout(callback, 0) 5 | } 6 | 7 | global.__DEV__ = true 8 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # EditorConfig is awesome: http://EditorConfig.org 2 | root = true 3 | 4 | [*] 5 | charset = utf-8 6 | indent_style = space 7 | indent_size = 2 8 | trim_trailing_whitespace = true 9 | end_of_line = lf 10 | insert_final_newline = true 11 | max_line_length = 120 12 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | !.eslintrc.js 2 | 3 | build 4 | lib 5 | node_modules 6 | 7 | *.d.ts 8 | -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "parser": "@typescript-eslint/parser", 3 | "plugins": ["@typescript-eslint", "react-hooks", "react"], 4 | "env": { 5 | "browser": true, 6 | "es6": true, 7 | "node": true 8 | }, 9 | "globals": { 10 | "__DEV__": true 11 | }, 12 | "parserOptions": { 13 | "ecmaVersion": 2018, 14 | "sourceType": "module", 15 | "ecmaFeatures": { "jsx": true }, 16 | "useJSXTextNode": true 17 | }, 18 | "settings": { 19 | "react": { 20 | "pragma": "React", 21 | "version": "16.0" 22 | } 23 | }, 24 | "rules": { 25 | // see https://github.com/airbnb/javascript/blob/master/packages/eslint-config-airbnb-base/rules/style.js#L122 26 | "indent": [ 27 | 2, 28 | 2, 29 | { 30 | "SwitchCase": 1, 31 | "VariableDeclarator": 1, 32 | "outerIIFEBody": 1, 33 | "FunctionDeclaration": { 34 | "parameters": 1, 35 | "body": 1 36 | }, 37 | "FunctionExpression": { 38 | "parameters": 1, 39 | "body": 1 40 | }, 41 | "CallExpression": { 42 | "arguments": 1 43 | }, 44 | "ArrayExpression": 1, 45 | "ObjectExpression": 1, 46 | "ImportDeclaration": 1, 47 | "flatTernaryExpressions": false, 48 | "ignoreComments": false 49 | } 50 | ], 51 | 52 | "semi": [2, "never"], 53 | "semi-spacing": [2, { "before": false, "after": true }], 54 | "wrap-iife": [2, "inside"], 55 | "no-use-before-define": [2, { "functions": true, "classes": true, "variables": true }], 56 | "no-caller": 2, 57 | "no-cond-assign": [2, "except-parens"], 58 | "no-constant-condition": 2, 59 | "no-debugger": 2, 60 | "no-dupe-args": 2, 61 | "no-dupe-keys": 2, 62 | "no-duplicate-case": 2, 63 | "no-empty": [2, { "allowEmptyCatch": true }], 64 | "no-extra-boolean-cast": 2, 65 | "no-extra-semi": 2, 66 | "no-func-assign": 2, 67 | "no-new": 2, 68 | "no-sparse-arrays": 2, 69 | "no-undef": 2, 70 | "no-unexpected-multiline": 2, 71 | "no-unreachable": 2, 72 | "no-unused-vars": [ 73 | 2, 74 | { 75 | "args": "after-used", 76 | "argsIgnorePattern": "^_", 77 | "ignoreRestSiblings": true, 78 | "vars": "all", 79 | "varsIgnorePattern": "^_" 80 | } 81 | ], 82 | 83 | "strict": 2, 84 | "max-params": [2, 5], 85 | "max-depth": [1, 4], 86 | "no-eq-null": 0, 87 | "no-unused-expressions": 0, 88 | "dot-notation": 2, 89 | "use-isnan": 2, 90 | 91 | // Best practices 92 | "block-scoped-var": 2, 93 | "complexity": [0, 11], 94 | "curly": [2, "multi-line"], 95 | "eqeqeq": [2, "always", { "null": "ignore" }], 96 | "no-else-return": 2, 97 | "no-extra-bind": 2, 98 | "no-implicit-coercion": 2, 99 | "no-return-assign": 0, 100 | "no-sequences": 2, 101 | "yoda": 2, 102 | 103 | // Variables 104 | "no-restricted-globals": [2, "fdescribe", "fit"], 105 | "no-var": 1, 106 | 107 | // Codestyle 108 | "arrow-parens": [2, "always"], 109 | "array-bracket-spacing": [2, "never"], 110 | "brace-style": [2, "1tbs", { "allowSingleLine": true }], 111 | "camelcase": [2, { "properties": "never" }], 112 | "comma-dangle": ["warn", "always-multiline"], 113 | "comma-spacing": [2, { "before": false, "after": true }], 114 | "eol-last": 2, 115 | "func-call-spacing": [2, "never"], 116 | "block-spacing": 2, 117 | "keyword-spacing": [2, { "before": true, "after": true }], 118 | "max-len": [ 119 | 2, 120 | { 121 | "code": 120, 122 | "ignoreUrls": true, 123 | "ignoreComments": false, 124 | "ignoreRegExpLiterals": true, 125 | "ignoreStrings": true, 126 | "ignoreTemplateLiterals": true, 127 | "ignorePattern": "require" 128 | } 129 | ], 130 | "no-lonely-if": 2, 131 | "no-mixed-spaces-and-tabs": 2, 132 | "no-multi-spaces": 2, 133 | "no-multiple-empty-lines": [2, { "max": 1, "maxBOF": 0, "maxEOF": 0 }], 134 | "no-trailing-spaces": 2, 135 | "no-unneeded-ternary": 2, 136 | "no-nested-ternary": 2, 137 | "object-curly-spacing": [2, "always"], 138 | "one-var-declaration-per-line": [2, "initializations"], 139 | "one-var": [2, { "let": "never", "const": "never" }], 140 | "operator-linebreak": [2, "before"], 141 | "padded-blocks": [2, "never"], 142 | "quote-props": [2, "as-needed", { "numbers": true }], 143 | "quotes": [2, "single", { "avoidEscape": true }], 144 | "space-before-blocks": [2, "always"], 145 | "space-before-function-paren": [ 146 | 2, 147 | { 148 | "asyncArrow": "always", 149 | "anonymous": "never", 150 | "named": "never" 151 | } 152 | ], 153 | "space-in-parens": 2, 154 | "no-console": [2, { "allow": ["assert", "error", "warn"] }], 155 | "key-spacing": [2, { "beforeColon": false, "afterColon": true, "mode": "strict" }], 156 | "space-infix-ops": 2, 157 | 158 | // REACT 159 | // https://github.com/yannickcr/eslint-plugin-react/tree/master/docs/rules 160 | "jsx-quotes": [2, "prefer-double"], 161 | "react/jsx-boolean-value": 2, 162 | "react/display-name": 0, 163 | "react/jsx-closing-tag-location": 2, 164 | "react/jsx-equals-spacing": 2, 165 | "react/jsx-first-prop-new-line": [2, "multiline"], 166 | "react/jsx-handler-names": 0, 167 | "react/jsx-indent": [2, 2], 168 | "react/jsx-indent-props": [2, 2], 169 | "react/jsx-key": 2, 170 | "react/jsx-no-bind": 1, 171 | "react/jsx-no-duplicate-props": 2, 172 | "react/jsx-no-literals": 0, 173 | "react/jsx-no-undef": 2, 174 | "react/jsx-sort-props": 0, 175 | "react/jsx-tag-spacing": [2, { "beforeClosing": "never", "beforeSelfClosing": "always" }], 176 | "react/jsx-uses-react": 2, 177 | "react/jsx-uses-vars": 2, 178 | "react/no-find-dom-node": 2, 179 | "react/no-multi-comp": 0, 180 | "react/no-set-state": 0, 181 | "react/react-in-jsx-scope": 2, 182 | "react/require-optimization": 0, 183 | "react/self-closing-comp": 2, 184 | "react/style-prop-object": 2, 185 | "react/void-dom-elements-no-children": 2, 186 | 187 | // https://github.com/typescript-eslint/typescript-eslint/tree/master/packages/eslint-plugin#supported-rules 188 | "@typescript-eslint/consistent-type-assertions": 2, 189 | "@typescript-eslint/no-empty-interface": 2, 190 | "@typescript-eslint/no-unused-vars": [ 191 | 2, 192 | { 193 | "args": "after-used", 194 | "argsIgnorePattern": "^_", 195 | "ignoreRestSiblings": true, 196 | "vars": "all", 197 | "varsIgnorePattern": "^_" 198 | } 199 | ], 200 | 201 | // https://reactjs.org/docs/hooks-rules.html 202 | "react-hooks/rules-of-hooks": 2, // Checks rules of Hooks 203 | "react-hooks/exhaustive-deps": 1 // Checks effect dependencies 204 | }, 205 | "overrides": [ 206 | { 207 | "files": ["*.test.{ts,tsx,js}"], 208 | "env": { 209 | "jest": true 210 | } 211 | }, 212 | { 213 | "files": ["*.{js,cjs}"], 214 | "rules": { 215 | "strict": 0 216 | } 217 | } 218 | ] 219 | } 220 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: ci 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | pull_request: 8 | branches: 9 | - master 10 | 11 | jobs: 12 | unit: 13 | runs-on: ubuntu-latest 14 | 15 | steps: 16 | - uses: actions/checkout@v3 17 | - uses: actions/setup-node@v3 18 | with: 19 | node-version: 18 20 | 21 | - name: install root-deps 22 | run: | 23 | npm i 24 | npm i -g lerna 25 | 26 | - name: build 27 | run: npm run build 28 | 29 | - name: units 30 | run: npm run unit 31 | env: 32 | CI: true 33 | 34 | linters: 35 | runs-on: ubuntu-latest 36 | 37 | steps: 38 | - uses: actions/checkout@v3 39 | with: 40 | fetch-depth: 0 41 | 42 | - uses: actions/setup-node@v3 43 | with: 44 | node-version: 18 45 | 46 | - name: install root-deps 47 | run: npm i --ignore-scripts 48 | 49 | - name: commitlint 50 | run: npx commitlint --from ${{ github.event.pull_request.base.sha }} --to ${{ github.event.pull_request.head.sha }} --verbose 51 | 52 | - name: eslint 53 | run: npm run lint 54 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .nyc_output 2 | coverage 3 | /lib 4 | umd 5 | node_modules 6 | *.log 7 | reference 8 | 9 | # Rollup 10 | 11 | .rpt2_cache 12 | 13 | # Build 14 | 15 | build 16 | -------------------------------------------------------------------------------- /.huskyrc: -------------------------------------------------------------------------------- 1 | { 2 | "hooks": { 3 | "pre-commit": "lint-staged", 4 | "commit-msg": "commitlint -E HUSKY_GIT_PARAMS" 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /.lintstagedrc: -------------------------------------------------------------------------------- 1 | { 2 | "*.{js,ts,tsx}": [ 3 | "prettier --write", 4 | "eslint --fix", 5 | "git add" 6 | ], 7 | "*.md": [ 8 | "prettier --write --parser markdown", 9 | "git add" 10 | ], 11 | "*.json": [ 12 | "prettier --write --parser json", 13 | "git add" 14 | ] 15 | } 16 | -------------------------------------------------------------------------------- /.npmrc: -------------------------------------------------------------------------------- 1 | package-lock=false 2 | save=false 3 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "arrowParens": "always", 3 | "endOfLine": "lf", 4 | "parser": "typescript", 5 | "printWidth": 100, 6 | "semi": false, 7 | "singleQuote": true, 8 | "tabWidth": 2, 9 | "trailingComma": "all" 10 | } 11 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | Bem React Core is an open source library that is under active development and is also used within [Yandex](https://yandex.com/company/). 4 | 5 | All work on Bem React Core is done directly on GitHub. Members of the core group as well other participants send [Pull Requests](https://github.com/bem/bem-react-core/pulls) that go through the same verification process. 6 | 7 | Development is carried out in branches. The main branch is `master`. The code in the `master`branch has been tested and is recommended for use. 8 | 9 | To make changes: 10 | 11 | 1. [Create an issue](#creating-an-issue) 12 | 2. [Send your Pull Request](#sending-a-pull-request) 13 | 14 | ## Creating an issue 15 | 16 | If you found a bug or want to make an improvement in the API: 17 | 18 | 1. First check whether the same issue already exists in the [list of issues](https://github.com/bem/bem-react-core/issues). 19 | 2. If you don't find the issue there, [create a new one](https://github.com/bem/bem-react-core/issues/new) including a description of the problem. 20 | 21 | > **Note:** Languages other than English are not normally used in issue descriptions. 22 | 23 | ## Sending a Pull Request 24 | 25 | To make changes to the library: 26 | 27 | 1. Fork the repository. 28 | 2. Clone the fork. 29 | 30 | ```bash 31 | $ git clone git@github.com:/bem-react-core.git 32 | ``` 33 | 34 | 3. Add the main repository for the `bem-react-core` library as a remote repository with the name "upstream". 35 | 36 | ```bash 37 | $ cd bem-react-core 38 | $ git remote add upstream git@github.com:bem/bem-react-core.git 39 | ``` 40 | 41 | 4. Fetch the latest changes. 42 | 43 | ```bash 44 | $ git fetch upstream 45 | ``` 46 | 47 | > **Note:** Repeat this step before every change you make, to be sure that you are working with code that contains the latest updates. 48 | 49 | 5. Create a `feature-branch` that includes the number of the [created issue](#creating-an-issue). 50 | 51 | ```bash 52 | $ git checkout upstream/master 53 | $ git checkout -b issue- 54 | ``` 55 | 56 | 6. Make changes. 57 | 7. Record the changes made by making comments in accordance with [Conventional Commits](https://conventionalcommits.org). 58 | 59 | ```bash 60 | $ git commit -m "[optional scope]: " 61 | ``` 62 | 63 | 8. Fetch the latest changes. 64 | 65 | ```bash 66 | $ git pull --rebase upstream master 67 | ``` 68 | 69 | 9. Send the changes to GitHub. 70 | 71 | ```bash 72 | $ git push -u origin issue- 73 | ``` 74 | 75 | 10. Send a [Pull Request](https://github.com/bem/bem-react-core/compare) based on the branch created. 76 | 11. Link the Pull Request and issue (for example, with a comment). 77 | 12. Wait for a decision about accepting the changes. 78 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # bem-react · [![github (ci)](https://github.com/bem/bem-react/workflows/ci/badge.svg?branch=master)](https://github.com/bem/bem-react/workflows/ci/badge.svg?branch=master) 2 | 3 | A set of tools for developing user interfaces using the [BEM methodology](https://en.bem.info) in [React](https://github.com/facebook/react). BEM React supports [TypeScript](https://www.typescriptlang.org/index.html) type annotations. 4 | 5 | ## Packages 6 | 7 | | Package | Version | Size | 8 | | ---------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------- | 9 | | [`@bem-react/classname`](packages/classname) | [![npm (scoped)](https://img.shields.io/npm/v/@bem-react/classname.svg)](https://www.npmjs.com/package/@bem-react/classname) | [![npm bundle size (minified + gzip)](https://img.shields.io/bundlephobia/minzip/@bem-react/classname.svg)](https://bundlephobia.com/result?p=@bem-react/classname) | 10 | | [`@bem-react/classnames`](packages/classnames) | [![npm (scoped)](https://img.shields.io/npm/v/@bem-react/classnames.svg)](https://www.npmjs.com/package/@bem-react/classnames) | [![npm bundle size (minified + gzip)](https://img.shields.io/bundlephobia/minzip/@bem-react/classnames.svg)](https://bundlephobia.com/result?p=@bem-react/classnames) | 11 | | [`@bem-react/core`](packages/core) | [![npm (scoped)](https://img.shields.io/npm/v/@bem-react/core.svg)](https://www.npmjs.com/package/@bem-react/core) | [![npm bundle size (minified + gzip)](https://img.shields.io/bundlephobia/minzip/@bem-react/core.svg)](https://bundlephobia.com/result?p=@bem-react/core) | 12 | | [`@bem-react/di`](packages/di) | [![npm (scoped)](https://img.shields.io/npm/v/@bem-react/di.svg)](https://www.npmjs.com/package/@bem-react/di) | [![npm bundle size (minified + gzip)](https://img.shields.io/bundlephobia/minzip/@bem-react/di.svg)](https://bundlephobia.com/result?p=@bem-react/di) | 13 | | [`@bem-react/eslint-plugin`](packages/eslint-plugin) | [![npm (scoped)](https://img.shields.io/npm/v/@bem-react/eslint-plugin.svg)](https://www.npmjs.com/package/@bem-react/eslint-plugin) | — | 14 | 15 | ## Contribute 16 | 17 | Bem React Core is an open source library that is under active development and is also used within [Yandex](https://yandex.com/company/). 18 | 19 | If you have suggestions for improving the API, you can send us a [Pull Request](https://github.com/bem/bem-react-core/pulls). 20 | 21 | If you found a bug, you can create an [issue](https://github.com/bem/bem-react-core/issues) describing the problem. 22 | 23 | For a detailed guide to making suggestions, see: [CONTRIBUTING.md](CONTRIBUTING.md). 24 | 25 | ## License 26 | 27 | © 2018 [Yandex](https://yandex.com/company/). Code released under [Mozilla Public License 2.0](LICENSE.md). 28 | -------------------------------------------------------------------------------- /environment.d.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Eenvironment flag for development. 3 | * @internal 4 | */ 5 | declare const __DEV__: boolean 6 | -------------------------------------------------------------------------------- /lerna.json: -------------------------------------------------------------------------------- 1 | { 2 | "packages": ["packages/*"], 3 | "command": { 4 | "publish": { 5 | "allowBranch": "master", 6 | "ignoreChanges": ["*.md", "**/test/**"], 7 | "message": "chore(release): publish" 8 | } 9 | }, 10 | "independent": true, 11 | "conventionalCommits": true, 12 | "version": "independent", 13 | "exact": true, 14 | "npmClientArgs": ["--no-package-lock"] 15 | } 16 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "private": true, 3 | "scripts": { 4 | "bootstrap": "lerna bootstrap --no-ci", 5 | "build": "lerna run build --concurrency=1", 6 | "lint": "eslint --ext .js,.cjs,.ts,.tsx .", 7 | "postinstall": "npm run bootstrap", 8 | "publish:next": "lerna publish --canary --preid dev --npm-tag next --no-git-tag-version", 9 | "unit:coverage": "npm run unit -- --coverage", 10 | "unit": "jest --config .config/jest/jest.config.js" 11 | }, 12 | "devDependencies": { 13 | "@commitlint/cli": "17.3.0", 14 | "@commitlint/config-conventional": "17.3.0", 15 | "@types/jest": "26.0.14", 16 | "@types/react": "18.0.26", 17 | "@typescript-eslint/eslint-plugin": "2.22.0", 18 | "@typescript-eslint/parser": "2.22.0", 19 | "chalk": "2.4.1", 20 | "eslint": "6.8.0", 21 | "eslint-plugin-react": "7.19.0", 22 | "eslint-plugin-react-hooks": "2.5.0", 23 | "gzip-size": "5.1.0", 24 | "husky": "3.0.5", 25 | "jest": "29.3.1", 26 | "jest-environment-jsdom": "29.3.1", 27 | "@testing-library/react": "13.4.0", 28 | "lerna": "3.5.1", 29 | "lint-staged": "9.2.5", 30 | "prettier": "2.8.1", 31 | "pretty-bytes": "5.2.0", 32 | "react": "18.2.0", 33 | "react-dom": "18.2.0", 34 | "rollup": "1.10.1", 35 | "rollup-plugin-node-resolve": "4.2.3", 36 | "rollup-plugin-replace": "2.1.0", 37 | "rollup-plugin-strip-banner": "0.2.0", 38 | "rollup-plugin-terser": "4.0.4", 39 | "rollup-plugin-typescript2": "0.21.0", 40 | "ts-jest": "29.0.3", 41 | "typescript": "4.9.4", 42 | "tslib": "2.4.1" 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /packages/classname/.gitignore: -------------------------------------------------------------------------------- 1 | classname.d.ts 2 | -------------------------------------------------------------------------------- /packages/classname/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Change Log 2 | 3 | All notable changes to this project will be documented in this file. 4 | See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. 5 | 6 | # [1.6.0](https://github.com/bem/bem-react/compare/@bem-react/classname@1.5.12...@bem-react/classname@1.6.0) (2023-04-04) 7 | 8 | ### Features 9 | 10 | - allow mix to be a string ([c1c9731](https://github.com/bem/bem-react/commit/c1c97312b80d24da88eafffd8e7c235887d65bd6)) 11 | 12 | ## [1.5.12](https://github.com/bem/bem-react/compare/@bem-react/classname@1.5.11...@bem-react/classname@1.5.12) (2021-09-03) 13 | 14 | ### Bug Fixes 15 | 16 | - **classname:** add correct overloads for cn formatter ([47758e7](https://github.com/bem/bem-react/commit/47758e76847e34babddbb99b60fd3f9827f49e95)) 17 | 18 | ## [1.5.11](https://github.com/bem/bem-react/compare/@bem-react/classname@1.5.10...@bem-react/classname@1.5.11) (2021-06-08) 19 | 20 | ### Bug Fixes 21 | 22 | - update pkg ([1ccdee8](https://github.com/bem/bem-react/commit/1ccdee8d9c4c09a02f888ee880a332ac75b725fd)) 23 | 24 | ## [1.5.10](https://github.com/bem/bem-react/compare/@bem-react/classname@1.5.9...@bem-react/classname@1.5.10) (2021-04-29) 25 | 26 | ### Bug Fixes 27 | 28 | - **classname:** formatter type ([04e8b5c](https://github.com/bem/bem-react/commit/04e8b5c00724e3817b624acf108a7186e94af200)) 29 | 30 | ## [1.5.9](https://github.com/bem/bem-react/compare/@bem-react/classname@1.5.8...@bem-react/classname@1.5.9) (2021-02-11) 31 | 32 | ### Bug Fixes 33 | 34 | - **classname:** support mix via valueOf method ([243575c](https://github.com/bem/bem-react/commit/243575c918f7e157d45f2379ef5c31c16975b8ab)) 35 | 36 | ## [1.5.8](https://github.com/bem/bem-react/compare/@bem-react/classname@1.5.7...@bem-react/classname@1.5.8) (2020-03-12) 37 | 38 | **Note:** Version bump only for package @bem-react/classname 39 | 40 | ## [1.5.7](https://github.com/bem/bem-react/compare/@bem-react/classname@1.5.6...@bem-react/classname@1.5.7) (2020-03-02) 41 | 42 | **Note:** Version bump only for package @bem-react/classname 43 | 44 | ## [1.5.6](https://github.com/bem/bem-react/compare/@bem-react/classname@1.5.5...@bem-react/classname@1.5.6) (2019-10-02) 45 | 46 | **Note:** Version bump only for package @bem-react/classname 47 | 48 | ## [1.5.5](https://github.com/bem/bem-react/compare/@bem-react/classname@1.5.4...@bem-react/classname@1.5.5) (2019-10-02) 49 | 50 | **Note:** Version bump only for package @bem-react/classname 51 | 52 | ## [1.5.4](https://github.com/bem/bem-react/compare/@bem-react/classname@1.5.3...@bem-react/classname@1.5.4) (2019-08-20) 53 | 54 | **Note:** Version bump only for package @bem-react/classname 55 | 56 | ## [1.5.3](https://github.com/bem/bem-react/compare/@bem-react/classname@1.5.2...@bem-react/classname@1.5.3) (2019-07-31) 57 | 58 | ### Bug Fixes 59 | 60 | - **classname:** mix as not string type ([9648499](https://github.com/bem/bem-react/commit/9648499)) 61 | 62 | ## [1.5.2](https://github.com/bem/bem-react/compare/@bem-react/classname@1.5.1...@bem-react/classname@1.5.2) (2019-05-27) 63 | 64 | **Note:** Version bump only for package @bem-react/classname 65 | 66 | ## [1.5.1](https://github.com/bem/bem-react/tree/master/packages/classname/compare/@bem-react/classname@1.5.0...@bem-react/classname@1.5.1) (2019-05-24) 67 | 68 | **Note:** Version bump only for package @bem-react/classname 69 | 70 | # [1.5.0](https://github.com/bem/bem-react/tree/master/packages/classname/compare/@bem-react/classname@1.4.4...@bem-react/classname@1.5.0) (2019-04-22) 71 | 72 | ### Features 73 | 74 | - **classname:** add modVal delimiter for withNaming configuration ([5fb63dc](https://github.com/bem/bem-react/commit/5fb63dc)) 75 | 76 | ## [1.4.4](https://github.com/bem/bem-react/tree/master/packages/classname/compare/@bem-react/classname@1.4.3...@bem-react/classname@1.4.4) (2019-03-01) 77 | 78 | **Note:** Version bump only for package @bem-react/classname 79 | 80 | ## [1.4.3](https://github.com/bem/bem-react/tree/master/packages/classname/compare/@bem-react/classname@1.4.2...@bem-react/classname@1.4.3) (2019-01-17) 81 | 82 | ### Bug Fixes 83 | 84 | - **classname:** filter class name duplicates with mods ([1cfb22c](https://github.com/bem/bem-react/commit/1cfb22c)) 85 | 86 | ## [1.4.2](https://github.com/bem/bem-react/tree/master/packages/classname/compare/@bem-react/classname@1.4.1...@bem-react/classname@1.4.2) (2019-01-17) 87 | 88 | ### Bug Fixes 89 | 90 | - **classname:** filter class name duplicates ([e92cbab](https://github.com/bem/bem-react/commit/e92cbab)) 91 | 92 | ## [1.4.1](https://github.com/bem/bem-react/tree/master/packages/classname/compare/@bem-react/classname@1.4.0...@bem-react/classname@1.4.1) (2018-12-28) 93 | 94 | **Note:** Version bump only for package @bem-react/classname 95 | 96 | # 1.4.0 (2018-12-21) 97 | 98 | ### Bug Fixes 99 | 100 | - classname with undefined ([9232c61](https://github.com/bem/bem-react/commit/9232c61)) 101 | - fix filename ([3dbdcdd](https://github.com/bem/bem-react/commit/3dbdcdd)) 102 | - **classname:** remove undefined modifiers ([c3af486](https://github.com/bem/bem-react/commit/c3af486)) 103 | - **classname:** remove whitespace after mix ([5e423e7](https://github.com/bem/bem-react/commit/5e423e7)) 104 | - **classname:** use set for unique class list ([9a708b1](https://github.com/bem/bem-react/commit/9a708b1)) 105 | 106 | ### Features 107 | 108 | - **classname:** array type for mix ([9513c26](https://github.com/bem/bem-react/commit/9513c26)) 109 | - **classname:** carry elems ([a943509](https://github.com/bem/bem-react/commit/a943509)) 110 | - **classname:** decrease bundle size, classnames pkg ([c5fb74f](https://github.com/bem/bem-react/commit/c5fb74f)) 111 | - **v3:** init packages ([c70a97d](https://github.com/bem/bem-react/commit/c70a97d)) 112 | - **v3:** init packages ([d652328](https://github.com/bem/bem-react/commit/d652328)) 113 | 114 | ## 1.3.2 (2018-12-21) 115 | 116 | ### Bug Fixes 117 | 118 | - classname with undefined ([9232c61](https://github.com/bem/bem-react/commit/9232c61)) 119 | - fix filename ([3dbdcdd](https://github.com/bem/bem-react/commit/3dbdcdd)) 120 | - **classname:** remove undefined modifiers ([6a595d9](https://github.com/bem/bem-react/commit/6a595d9)) 121 | - **classname:** remove whitespace after mix ([2a9f8f0](https://github.com/bem/bem-react/commit/2a9f8f0)) 122 | - **classname:** use set for unique class list ([9a708b1](https://github.com/bem/bem-react/commit/9a708b1)) 123 | 124 | ### Features 125 | 126 | - **classname:** array type for mix ([9513c26](https://github.com/bem/bem-react/commit/9513c26)) 127 | - **classname:** carry elems ([a943509](https://github.com/bem/bem-react/commit/a943509)) 128 | - **classname:** decrease bundle size, classnames pkg ([c5fb74f](https://github.com/bem/bem-react/commit/c5fb74f)) 129 | - **v3:** init packages ([c70a97d](https://github.com/bem/bem-react/commit/c70a97d)) 130 | - **v3:** init packages ([d652328](https://github.com/bem/bem-react/commit/d652328)) 131 | 132 | ## 1.3.1 (2018-12-19) 133 | 134 | ### Bug Fixes 135 | 136 | - **classname:** use set for unique class list ([9a708b1](https://github.com/bem/bem-react/commit/9a708b1)) 137 | - classname with undefined ([9232c61](https://github.com/bem/bem-react/commit/9232c61)) 138 | - fix filename ([3dbdcdd](https://github.com/bem/bem-react/commit/3dbdcdd)) 139 | 140 | ### Features 141 | 142 | - **classname:** array type for mix ([9513c26](https://github.com/bem/bem-react/commit/9513c26)) 143 | - **classname:** carry elems ([a943509](https://github.com/bem/bem-react/commit/a943509)) 144 | - **classname:** decrease bundle size, classnames pkg ([c5fb74f](https://github.com/bem/bem-react/commit/c5fb74f)) 145 | - **v3:** init packages ([c70a97d](https://github.com/bem/bem-react/commit/c70a97d)) 146 | - **v3:** init packages ([d652328](https://github.com/bem/bem-react/commit/d652328)) 147 | 148 | # 1.3.0 (2018-12-18) 149 | 150 | ### Bug Fixes 151 | 152 | - **classname:** use set for unique class list ([9a708b1](https://github.com/bem/bem-react/commit/9a708b1)) 153 | - classname with undefined ([9232c61](https://github.com/bem/bem-react/commit/9232c61)) 154 | - fix filename ([3dbdcdd](https://github.com/bem/bem-react/commit/3dbdcdd)) 155 | 156 | ### Features 157 | 158 | - **classname:** array type for mix ([9513c26](https://github.com/bem/bem-react/commit/9513c26)) 159 | - **classname:** carry elems ([a943509](https://github.com/bem/bem-react/commit/a943509)) 160 | - **classname:** decrease bundle size, classnames pkg ([c5fb74f](https://github.com/bem/bem-react/commit/c5fb74f)) 161 | - **v3:** init packages ([c70a97d](https://github.com/bem/bem-react/commit/c70a97d)) 162 | - **v3:** init packages ([d652328](https://github.com/bem/bem-react/commit/d652328)) 163 | 164 | # 1.2.0 (2018-12-06) 165 | 166 | ### Bug Fixes 167 | 168 | - **classname:** use set for unique class list ([9a708b1](https://github.com/bem/bem-react/commit/9a708b1)) 169 | - classname with undefined ([9232c61](https://github.com/bem/bem-react/commit/9232c61)) 170 | - fix filename ([3dbdcdd](https://github.com/bem/bem-react/commit/3dbdcdd)) 171 | 172 | ### Features 173 | 174 | - **classname:** array type for mix ([9513c26](https://github.com/bem/bem-react/commit/9513c26)) 175 | - **classname:** carry elems ([a943509](https://github.com/bem/bem-react/commit/a943509)) 176 | - **classname:** decrease bundle size, classnames pkg ([c5fb74f](https://github.com/bem/bem-react/commit/c5fb74f)) 177 | - **v3:** init packages ([c70a97d](https://github.com/bem/bem-react/commit/c70a97d)) 178 | - **v3:** init packages ([d652328](https://github.com/bem/bem-react/commit/d652328)) 179 | 180 | 181 | 182 | ## [1.1.1](https://github.com/bem/bem-react-core/compare/@bem-react/classname@1.1.0...@bem-react/classname@1.1.1) (2018-10-24) 183 | 184 | ### Bug Fixes 185 | 186 | - **classname:** use set for unique class list ([6f4aa5f](https://github.com/bem/bem-react-core/commit/6f4aa5f)) 187 | - classname with undefined ([5f0e907](https://github.com/bem/bem-react-core/commit/5f0e907)) 188 | 189 | 190 | 191 | # [1.1.0](https://github.com/bem/bem-react-core/compare/@bem-react/classname@0.3.2...@bem-react/classname@1.1.0) (2018-10-02) 192 | 193 | ### Features 194 | 195 | - **classname:** array type for mix ([dd985e8](https://github.com/bem/bem-react-core/commit/dd985e8)) 196 | 197 | 198 | 199 | # [1.0.0](https://github.com/bem/bem-react-core/compare/@bem-react/classname@0.3.2...@bem-react/classname@1.0.0) (2018-09-20) 200 | 201 | **Note:** Version bump only for package @bem-react/classname 202 | 203 | 204 | 205 | ## [0.3.2](https://github.com/bem/bem-react-core/compare/@bem-react/classname@0.3.1...@bem-react/classname@0.3.2) (2018-09-04) 206 | 207 | **Note:** Version bump only for package @bem-react/classname 208 | 209 | 210 | 211 | ## [0.3.1](https://github.com/bem/bem-react-core/compare/@bem-react/classname@0.3.0...@bem-react/classname@0.3.1) (2018-08-30) 212 | 213 | **Note:** Version bump only for package @bem-react/classname 214 | 215 | 216 | 217 | # [0.3.0](https://github.com/bem/bem-react-core/compare/@bem-react/classname@0.2.2...@bem-react/classname@0.3.0) (2018-08-30) 218 | 219 | ### Features 220 | 221 | - **classname:** carry elems ([81f28c3](https://github.com/bem/bem-react-core/commit/81f28c3)) 222 | 223 | 224 | 225 | ## [0.2.2](https://github.com/bem/bem-react-core/compare/@bem-react/classname@0.2.1...@bem-react/classname@0.2.2) (2018-08-30) 226 | 227 | **Note:** Version bump only for package @bem-react/classname 228 | 229 | 230 | 231 | ## [0.2.1](https://github.com/bem/bem-react-core/compare/@bem-react/classname@0.2.0...@bem-react/classname@0.2.1) (2018-08-29) 232 | 233 | **Note:** Version bump only for package @bem-react/classname 234 | 235 | 236 | 237 | # 0.2.0 (2018-08-29) 238 | 239 | ### Bug Fixes 240 | 241 | - fix filename ([ee0f862](https://github.com/bem/bem-react-core/commit/ee0f862)) 242 | 243 | ### Features 244 | 245 | - **v3:** init packages ([00423c8](https://github.com/bem/bem-react-core/commit/00423c8)) 246 | - **v3:** init packages ([b192fc5](https://github.com/bem/bem-react-core/commit/b192fc5)) 247 | 248 | 249 | 250 | # 0.1.0 (2018-08-29) 251 | 252 | ### Bug Fixes 253 | 254 | - fix filename ([ee0f862](https://github.com/bem/bem-react-core/commit/ee0f862)) 255 | 256 | ### Features 257 | 258 | - **v3:** init packages ([00423c8](https://github.com/bem/bem-react-core/commit/00423c8)) 259 | - **v3:** init packages ([b192fc5](https://github.com/bem/bem-react-core/commit/b192fc5)) 260 | -------------------------------------------------------------------------------- /packages/classname/README.md: -------------------------------------------------------------------------------- 1 | # @bem-react/classname · [![npm (scoped)](https://img.shields.io/npm/v/@bem-react/classname.svg)](https://www.npmjs.com/package/@bem-react/classname) [![npm bundle size (minified + gzip)](https://img.shields.io/bundlephobia/minzip/@bem-react/classname.svg)](https://bundlephobia.com/result?p=@bem-react/classname) 2 | 3 | Tiny helper for building CSS classes with BEM methodology. 4 | 5 | ## Install 6 | 7 | ``` 8 | npm i -S @bem-react/classname 9 | ``` 10 | 11 | ## Usage 12 | 13 | ```js 14 | import { cn } from '@bem-react/classname' 15 | 16 | const cat = cn('Cat') 17 | 18 | cat() // Cat 19 | cat({ size: 'm' }) // Cat Cat_size_m 20 | cat('Tail') // Cat-Tail 21 | cat('Tail', { length: 'small' }) // Cat-Tail Cat-Tail_length_small 22 | 23 | const dogPaw = cn('Dog', 'Paw') 24 | 25 | dogPaw() // Dog-Paw 26 | dogPaw({ color: 'black', exists: true }) // Dog-Paw Dog-Paw_color_black Dog-Paw_exists 27 | 28 | // mixes 29 | 30 | cat(null, ['Dog']) // Cat Dog 31 | cat({ size: 'm' }, ['Dog', 'Horse']) // Cat Cat_size_m Dog Horse 32 | 33 | cat('Tail', [dogPaw()]) // Cat-Tail Dog-Paw 34 | cat('Tail', { length: 'small' }, [dogPaw({ color: 'black' })]) // Cat-Tail Cat-Tail_length_small Dog-Paw Dog-Paw_color_black 35 | ``` 36 | 37 | ## Configure 38 | 39 | By default `classname` uses React naming preset. But it's possible to use any. 40 | 41 | ```js 42 | import { withNaming } from '@bem-react/classname' 43 | 44 | const cn = withNaming({ n: 'ns-', e: '__', m: '_', v: '_' }) 45 | 46 | cn('block', 'elem')({ theme: 'default' }) // ns-block__elem_theme_default 47 | ``` 48 | -------------------------------------------------------------------------------- /packages/classname/classname.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * List of classname. 3 | */ 4 | export type ClassNameList = string | Array 5 | 6 | /** 7 | * Allowed modifiers format. 8 | * 9 | * @see https://en.bem.info/methodology/key-concepts/#modifier 10 | */ 11 | export type NoStrictEntityMods = Record 12 | 13 | /** 14 | * BEM Entity className initializer. 15 | */ 16 | export type ClassNameInitilizer = (blockName: string, elemName?: string) => ClassNameFormatter 17 | 18 | /** 19 | * BEM Entity className formatter. 20 | */ 21 | export interface ClassNameFormatter { 22 | (): string 23 | (mods?: NoStrictEntityMods | null, mix?: ClassNameList): string 24 | (elemName: string, elemMix?: ClassNameList): string 25 | (elemName: string, elemMods?: NoStrictEntityMods | null, elemMix?: ClassNameList): string 26 | } 27 | 28 | /** 29 | * Settings for the naming convention. 30 | */ 31 | export type Preset = { 32 | /** 33 | * Global namespace. 34 | * 35 | * @example `omg-Bem-Elem_mod_val` 36 | */ 37 | n?: string 38 | /** 39 | * Elem's delimiter. 40 | */ 41 | e?: string 42 | /** 43 | * Modifier's delimiter. 44 | */ 45 | m?: string 46 | /** 47 | * Modifier value delimiter. 48 | */ 49 | v?: string 50 | } 51 | 52 | /** 53 | * BEM className configure function. 54 | * 55 | * @example 56 | * ``` ts 57 | * 58 | * import { withNaming } from '@bem-react/classname'; 59 | * 60 | * const cn = withNaming({ n: 'ns-', e: '__', m: '_' }); 61 | * 62 | * cn('block', 'elem'); // 'ns-block__elem' 63 | * ``` 64 | * 65 | * @param preset settings for the naming convention 66 | */ 67 | export function withNaming(preset: Preset): ClassNameInitilizer { 68 | const nameSpace = preset.n || '' 69 | const modValueDelimiter = preset.v || preset.m 70 | 71 | function stringify(b: string, e?: string, m?: NoStrictEntityMods | null, mix?: ClassNameList) { 72 | const entityName = e ? nameSpace + b + preset.e + e : nameSpace + b 73 | let className = entityName 74 | 75 | if (m) { 76 | const modPrefix = ' ' + className + preset.m 77 | 78 | for (const k in m) { 79 | if (m.hasOwnProperty(k)) { 80 | const modVal = m[k] 81 | 82 | if (modVal === true) { 83 | className += modPrefix + k 84 | } else if (modVal) { 85 | className += modPrefix + k + modValueDelimiter + modVal 86 | } 87 | } 88 | } 89 | } 90 | 91 | if (mix !== undefined) { 92 | mix = Array.isArray(mix) ? mix : [mix] 93 | 94 | for (let i = 0, len = mix.length; i < len; i++) { 95 | const value = mix[i] 96 | 97 | // Skipping non-string values and empty strings 98 | if (!value || typeof value.valueOf() !== 'string') continue 99 | 100 | const mixes = value.valueOf().split(' ') 101 | 102 | for (let j = 0; j < mixes.length; j++) { 103 | const val = mixes[j] 104 | if (val !== entityName) { 105 | className += ' ' + val 106 | } 107 | } 108 | } 109 | } 110 | 111 | return className 112 | } 113 | 114 | return function cnGenerator(b: string, e?: string): ClassNameFormatter { 115 | return ( 116 | elemOrMods?: NoStrictEntityMods | string | null, 117 | elemModsOrBlockMix?: NoStrictEntityMods | ClassNameList | null, 118 | elemMix?: ClassNameList, 119 | ) => { 120 | if (typeof elemOrMods === 'string') { 121 | if (typeof elemModsOrBlockMix === 'string' || Array.isArray(elemModsOrBlockMix)) { 122 | return stringify(b, elemOrMods, undefined, elemModsOrBlockMix) 123 | } 124 | return stringify(b, elemOrMods, elemModsOrBlockMix, elemMix) 125 | } 126 | return stringify(b, e, elemOrMods, elemModsOrBlockMix as ClassNameList) 127 | } 128 | } 129 | } 130 | 131 | /** 132 | * BEM Entity className initializer with React naming preset. 133 | * 134 | * @example 135 | * ``` ts 136 | * 137 | * import { cn } from '@bem-react/classname'; 138 | * 139 | * const cat = cn('Cat'); 140 | * 141 | * cat(); // Cat 142 | * cat({ size: 'm' }); // Cat_size_m 143 | * cat('Tail'); // Cat-Tail 144 | * cat('Tail', { length: 'small' }); // Cat-Tail_length_small 145 | * 146 | * const dogPaw = cn('Dog', 'Paw'); 147 | * 148 | * dogPaw(); // Dog-Paw 149 | * dogPaw({ color: 'black', exists: true }); // Dog-Paw_color_black Dog-Paw_exists 150 | * ``` 151 | * 152 | * @see https://en.bem.info/methodology/naming-convention/#react-style 153 | */ 154 | export const cn = withNaming({ 155 | e: '-', 156 | m: '_', 157 | }) 158 | -------------------------------------------------------------------------------- /packages/classname/index.cjs: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | if (process.env.NODE_ENV === 'production') { 4 | module.exports = require('./build/classname.production.min.cjs') 5 | } else { 6 | module.exports = require('./build/classname.development.cjs') 7 | } 8 | -------------------------------------------------------------------------------- /packages/classname/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@bem-react/classname", 3 | "version": "1.6.0", 4 | "description": "BEM React ClassName", 5 | "homepage": "https://github.com/bem/bem-react/tree/master/packages/classname", 6 | "repository": "https://github.com/bem/bem-react", 7 | "keywords": ["bem", "naming", "classes", "notation", "core"], 8 | "main": "index.cjs", 9 | "typings": "classname.d.ts", 10 | "exports": { 11 | "development": { 12 | "require": "./build/classname.development.cjs", 13 | "module": "./build/classname.development.mjs" 14 | }, 15 | "production": { 16 | "require": "./build/classname.production.min.cjs", 17 | "module": "./build/classname.production.min.mjs" 18 | }, 19 | "require": "./index.cjs", 20 | "default": "./build/classname.production.min.mjs" 21 | }, 22 | "publishConfig": { 23 | "access": "public" 24 | }, 25 | "files": ["build", "classname.d.ts"], 26 | "license": "MPL-2.0", 27 | "scripts": { 28 | "prepublishOnly": "npm run build", 29 | "build": "node ../../scripts/rollup/build.js", 30 | "unit": "../../node_modules/.bin/jest --config ../../.config/jest/jest.config.js" 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /packages/classname/test/classname.test.ts: -------------------------------------------------------------------------------- 1 | import { cn, withNaming } from '../classname' 2 | 3 | describe('@bem-react/classname', () => { 4 | describe('cn', () => { 5 | test('block', () => { 6 | const b = cn('Block') 7 | expect(b()).toEqual('Block') 8 | }) 9 | 10 | test('elem', () => { 11 | const e = cn('Block', 'Elem') 12 | expect(e()).toEqual('Block-Elem') 13 | }) 14 | 15 | describe('modifiers', () => { 16 | test('block', () => { 17 | const b = cn('Block') 18 | expect(b({ modName: true })).toEqual('Block Block_modName') 19 | }) 20 | 21 | test('elem', () => { 22 | const e = cn('Block', 'Elem') 23 | expect(e({ modName: true })).toEqual('Block-Elem Block-Elem_modName') 24 | }) 25 | 26 | test('more than one', () => { 27 | const mods = { modName: true, modName2: 'modVal' } 28 | const b = cn('Block') 29 | const e = cn('Block', 'Elem') 30 | 31 | expect(b(mods)).toEqual('Block Block_modName Block_modName2_modVal') 32 | expect(e(mods)).toEqual('Block-Elem Block-Elem_modName Block-Elem_modName2_modVal') 33 | }) 34 | 35 | test('empty', () => { 36 | const b = cn('Block') 37 | expect(b({})).toEqual('Block') 38 | }) 39 | 40 | test('falsy', () => { 41 | const b = cn('Block') 42 | expect(b({ modName: false })).toEqual('Block') 43 | }) 44 | 45 | test('with falsy', () => { 46 | const b = cn('Block', 'Elem') 47 | expect(b({ modName: false, mod: 'val' })).toEqual('Block-Elem Block-Elem_mod_val') 48 | }) 49 | 50 | test('zero', () => { 51 | const b = cn('Block') 52 | expect(b({ modName: '0' })).toEqual('Block Block_modName_0') 53 | }) 54 | 55 | test('undefined', () => { 56 | const b = cn('Block') 57 | expect(b({ modName: undefined })).toEqual('Block') 58 | }) 59 | }) 60 | 61 | describe('mix', () => { 62 | test('block', () => { 63 | const b = cn('Block') 64 | expect(b(null, 'Mix')).toEqual('Block Mix') 65 | expect(b(null, ['Mix1', 'Mix2'])).toEqual('Block Mix1 Mix2') 66 | }) 67 | 68 | test('block with mods', () => { 69 | const b = cn('Block') 70 | expect(b({ theme: 'normal' }, 'Mix')).toEqual('Block Block_theme_normal Mix') 71 | expect(b({ theme: 'normal' }, ['Mix'])).toEqual('Block Block_theme_normal Mix') 72 | }) 73 | 74 | test('elem', () => { 75 | const e = cn('Block', 'Elem') 76 | expect(e(null, 'Mix')).toEqual('Block-Elem Mix') 77 | expect(e(null, ['Mix1', 'Mix2'])).toEqual('Block-Elem Mix1 Mix2') 78 | }) 79 | 80 | test('elem with mods', () => { 81 | const e = cn('Block', 'Elem') 82 | expect(e({ theme: 'normal' }, 'Mix')).toEqual('Block-Elem Block-Elem_theme_normal Mix') 83 | expect(e({ theme: 'normal' }, ['Mix'])).toEqual('Block-Elem Block-Elem_theme_normal Mix') 84 | }) 85 | 86 | test('carry elem', () => { 87 | const b = cn('Block') 88 | expect(b('Elem', 'Mix')).toEqual('Block-Elem Mix') 89 | expect(b('Elem', ['Mix1', 'Mix2'])).toEqual('Block-Elem Mix1 Mix2') 90 | }) 91 | 92 | test('carry elem with mods', () => { 93 | const b = cn('Block') 94 | expect(b('Elem', { theme: 'normal' }, 'Mix')).toEqual( 95 | 'Block-Elem Block-Elem_theme_normal Mix', 96 | ) 97 | expect(b('Elem', { theme: 'normal' }, ['Mix'])).toEqual( 98 | 'Block-Elem Block-Elem_theme_normal Mix', 99 | ) 100 | }) 101 | 102 | test('undefined', () => { 103 | const b = cn('Block') 104 | expect(b('Elem', null, undefined)).toEqual('Block-Elem') 105 | expect(b('Elem', null, [undefined])).toEqual('Block-Elem') 106 | }) 107 | 108 | /** @see https://github.com/bem/bem-react/issues/445 */ 109 | test('not string and not undefined', () => { 110 | const b = cn('Block') 111 | expect(b('Elem', null, false as any)).toEqual('Block-Elem') 112 | expect(b('Elem', null, true as any)).toEqual('Block-Elem') 113 | expect(b('Elem', null, 10 as any)).toEqual('Block-Elem') 114 | expect(b('Elem', null, null as any)).toEqual('Block-Elem') 115 | expect(b('Elem', null, [false as any])).toEqual('Block-Elem') 116 | expect(b('Elem', null, [true as any])).toEqual('Block-Elem') 117 | expect(b('Elem', null, [10 as any])).toEqual('Block-Elem') 118 | expect(b('Elem', null, [null as any])).toEqual('Block-Elem') 119 | }) 120 | 121 | test('unique block', () => { 122 | const b = cn('Block') 123 | expect(b(null, 'Block')).toEqual('Block') 124 | expect(b(null, ['Block'])).toEqual('Block') 125 | }) 126 | 127 | test('unique block with mods', () => { 128 | const b = cn('Block') 129 | expect(b({ theme: 'normal' }, 'Block Block_size_m')).toEqual( 130 | 'Block Block_theme_normal Block_size_m', 131 | ) 132 | expect(b({ theme: 'normal' }, ['Block Block_size_m'])).toEqual( 133 | 'Block Block_theme_normal Block_size_m', 134 | ) 135 | }) 136 | 137 | test('unique elem', () => { 138 | const b = cn('Block') 139 | expect(b('Elem', null, 'Block-Elem')).toEqual('Block-Elem') 140 | expect(b('Elem', null, ['Block-Elem'])).toEqual('Block-Elem') 141 | }) 142 | 143 | test('unique elem with mods', () => { 144 | const b = cn('Block') 145 | expect(b('Elem', { theme: 'normal' }, 'Block-Elem Block-Elem_size_m')).toEqual( 146 | 'Block-Elem Block-Elem_theme_normal Block-Elem_size_m', 147 | ) 148 | expect(b('Elem', { theme: 'normal' }, ['Block-Elem Block-Elem_size_m'])).toEqual( 149 | 'Block-Elem Block-Elem_theme_normal Block-Elem_size_m', 150 | ) 151 | }) 152 | 153 | test('object with valueOf', () => { 154 | const b = cn('Block') 155 | expect(b('Elem', null, { valueOf: () => 'Mix' } as string)).toEqual('Block-Elem Mix') 156 | expect(b('Elem', null, [{ valueOf: () => 'Mix' } as string])).toEqual('Block-Elem Mix') 157 | }) 158 | }) 159 | }) 160 | 161 | describe('withNaming origin preset', () => { 162 | const cCn = withNaming({ 163 | e: '__', 164 | m: '_', 165 | }) 166 | 167 | test('block', () => { 168 | const b = cCn('block') 169 | expect(b()).toEqual('block') 170 | }) 171 | 172 | test('elem', () => { 173 | const e = cCn('block', 'elem') 174 | expect(e()).toEqual('block__elem') 175 | }) 176 | 177 | describe('modifiers', () => { 178 | test('block', () => { 179 | const b = cCn('block') 180 | expect(b({ modName: true })).toEqual('block block_modName') 181 | }) 182 | 183 | test('elem', () => { 184 | const e = cCn('block', 'elem') 185 | expect(e({ modName: true })).toEqual('block__elem block__elem_modName') 186 | }) 187 | 188 | test('more than one', () => { 189 | const mods = { modName: true, modName2: 'modVal' } 190 | const b = cCn('block') 191 | const e = cCn('block', 'elem') 192 | 193 | expect(b(mods)).toEqual('block block_modName block_modName2_modVal') 194 | expect(e(mods)).toEqual('block__elem block__elem_modName block__elem_modName2_modVal') 195 | }) 196 | 197 | test('empty', () => { 198 | const b = cCn('block') 199 | expect(b({})).toEqual('block') 200 | }) 201 | 202 | test('falsy', () => { 203 | const b = cCn('block') 204 | expect(b({ modName: false })).toEqual('block') 205 | }) 206 | 207 | test('with falsy', () => { 208 | const b = cCn('block') 209 | expect(b({ modName: false, mod: 'val' })).toEqual('block block_mod_val') 210 | }) 211 | 212 | test('zero', () => { 213 | const b = cCn('block') 214 | expect(b({ modName: '0' })).toEqual('block block_modName_0') 215 | }) 216 | }) 217 | }) 218 | 219 | describe('withNaming custom preset', () => { 220 | const customCn = withNaming({ 221 | e: '__', 222 | m: '--', 223 | v: '_', 224 | }) 225 | 226 | test('variants', () => { 227 | const block = customCn('block') 228 | 229 | expect(block({ mod: true })).toEqual('block block--mod') 230 | expect(block({ mod: false })).toEqual('block') 231 | expect(block({ mod: 'value' })).toEqual('block block--mod_value') 232 | expect(block('element', { mod: true })).toEqual('block__element block__element--mod') 233 | expect(block('element', { mod: false })).toEqual('block__element') 234 | expect(block('element', { mod: 'value' })).toEqual('block__element block__element--mod_value') 235 | }) 236 | }) 237 | 238 | describe('carry', () => { 239 | test('alone', () => { 240 | const e = cn('Block') 241 | expect(e('Elem')).toEqual('Block-Elem') 242 | }) 243 | 244 | test('with mods', () => { 245 | const e = cn('Block') 246 | expect(e('Elem', { modName: true })).toEqual('Block-Elem Block-Elem_modName') 247 | }) 248 | 249 | test('with elemMods', () => { 250 | const e = cn('Block', 'Elem') 251 | expect(e({ modName: true })).toEqual('Block-Elem Block-Elem_modName') 252 | }) 253 | }) 254 | }) 255 | -------------------------------------------------------------------------------- /packages/classname/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.json", 3 | "compilerOptions": { 4 | /* Basic Options */ 5 | "rootDir": ".", 6 | "declarationDir": ".", 7 | "outDir": "build" 8 | }, 9 | "include": ["../../environment.d.ts"] 10 | } 11 | -------------------------------------------------------------------------------- /packages/classnames/.gitignore: -------------------------------------------------------------------------------- 1 | classnames.d.ts 2 | -------------------------------------------------------------------------------- /packages/classnames/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Change Log 2 | 3 | All notable changes to this project will be documented in this file. 4 | See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. 5 | 6 | ## [1.3.10](https://github.com/bem/bem-react/compare/@bem-react/classnames@1.3.9...@bem-react/classnames@1.3.10) (2021-06-08) 7 | 8 | ### Bug Fixes 9 | 10 | - update pkg ([1ccdee8](https://github.com/bem/bem-react/commit/1ccdee8d9c4c09a02f888ee880a332ac75b725fd)) 11 | 12 | ## [1.3.9](https://github.com/bem/bem-react/compare/@bem-react/classnames@1.3.8...@bem-react/classnames@1.3.9) (2020-03-12) 13 | 14 | **Note:** Version bump only for package @bem-react/classnames 15 | 16 | ## [1.3.8](https://github.com/bem/bem-react/compare/@bem-react/classnames@1.3.7...@bem-react/classnames@1.3.8) (2020-03-02) 17 | 18 | **Note:** Version bump only for package @bem-react/classnames 19 | 20 | ## [1.3.7](https://github.com/bem/bem-react/compare/@bem-react/classnames@1.3.6...@bem-react/classnames@1.3.7) (2019-10-02) 21 | 22 | **Note:** Version bump only for package @bem-react/classnames 23 | 24 | ## [1.3.6](https://github.com/bem/bem-react/compare/@bem-react/classnames@1.3.5...@bem-react/classnames@1.3.6) (2019-10-02) 25 | 26 | **Note:** Version bump only for package @bem-react/classnames 27 | 28 | ## [1.3.5](https://github.com/bem/bem-react/compare/@bem-react/classnames@1.3.4...@bem-react/classnames@1.3.5) (2019-08-20) 29 | 30 | **Note:** Version bump only for package @bem-react/classnames 31 | 32 | ## [1.3.4](https://github.com/bem/bem-react/compare/@bem-react/classnames@1.3.3...@bem-react/classnames@1.3.4) (2019-05-27) 33 | 34 | **Note:** Version bump only for package @bem-react/classnames 35 | 36 | ## [1.3.3](https://github.com/bem/bem-react/tree/master/packages/classnames/compare/@bem-react/classnames@1.3.2...@bem-react/classnames@1.3.3) (2019-05-24) 37 | 38 | **Note:** Version bump only for package @bem-react/classnames 39 | 40 | ## [1.3.2](https://github.com/bem/bem-react/tree/master/packages/classnames/compare/@bem-react/classnames@1.3.1...@bem-react/classnames@1.3.2) (2019-04-22) 41 | 42 | **Note:** Version bump only for package @bem-react/classnames 43 | 44 | ## [1.3.1](https://github.com/bem/bem-react/tree/master/packages/classnames/compare/@bem-react/classnames@1.3.0...@bem-react/classnames@1.3.1) (2018-12-28) 45 | 46 | **Note:** Version bump only for package @bem-react/classnames 47 | 48 | # 1.3.0 (2018-12-21) 49 | 50 | ### Features 51 | 52 | - **classname:** decrease bundle size, classnames pkg ([c5fb74f](https://github.com/bem/bem-react/commit/c5fb74f)) 53 | 54 | ## 1.2.2 (2018-12-21) 55 | 56 | ### Features 57 | 58 | - **classname:** decrease bundle size, classnames pkg ([c5fb74f](https://github.com/bem/bem-react/commit/c5fb74f)) 59 | 60 | ## 1.2.1 (2018-12-19) 61 | 62 | ### Features 63 | 64 | - **classname:** decrease bundle size, classnames pkg ([c5fb74f](https://github.com/bem/bem-react/commit/c5fb74f)) 65 | 66 | # 1.2.0 (2018-12-18) 67 | 68 | ### Features 69 | 70 | - **classname:** decrease bundle size, classnames pkg ([c5fb74f](https://github.com/bem/bem-react/commit/c5fb74f)) 71 | 72 | # 1.1.0 (2018-12-06) 73 | 74 | ### Features 75 | 76 | - **classname:** decrease bundle size, classnames pkg ([c5fb74f](https://github.com/bem/bem-react/commit/c5fb74f)) 77 | -------------------------------------------------------------------------------- /packages/classnames/README.md: -------------------------------------------------------------------------------- 1 | # @bem-react/classnames · [![npm (scoped)](https://img.shields.io/npm/v/@bem-react/classname.svg)](https://www.npmjs.com/package/@bem-react/classnames) [![npm bundle size (minified + gzip)](https://img.shields.io/bundlephobia/minzip/@bem-react/classnames.svg)](https://bundlephobia.com/result?p=@bem-react/classnames) 2 | 3 | Tiny helper for merging CSS classes. 4 | 5 | ## Install 6 | 7 | ``` 8 | npm i -S @bem-react/classnames 9 | ``` 10 | 11 | ## Usage 12 | 13 | ```ts 14 | import { classnames } from '@bem-react/classnames' 15 | 16 | classnames('Block', undefined, 'Block2', 'Block') // Block Block2 17 | ``` 18 | -------------------------------------------------------------------------------- /packages/classnames/classnames.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Generate className string with unique tokens. 3 | * 4 | * @example 5 | * classnames('Button', 'Header-Button', undefined) // -> Button Header-Button 6 | * 7 | * @param tokens ClassNames tokens. 8 | */ 9 | export function classnames(...tokens: Array): string { 10 | let className = '' 11 | const uniqueCache = new Set() 12 | const classNameList = tokens.join(' ').split(' ') 13 | 14 | for (const value of classNameList) { 15 | if (value === '' || uniqueCache.has(value)) { 16 | continue 17 | } 18 | 19 | uniqueCache.add(value) 20 | 21 | if (className.length > 0) { 22 | className += ' ' 23 | } 24 | 25 | className += value 26 | } 27 | 28 | return className 29 | } 30 | -------------------------------------------------------------------------------- /packages/classnames/index.cjs: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | if (process.env.NODE_ENV === 'production') { 4 | module.exports = require('./build/classnames.production.min.cjs') 5 | } else { 6 | module.exports = require('./build/classnames.development.cjs') 7 | } 8 | -------------------------------------------------------------------------------- /packages/classnames/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@bem-react/classnames", 3 | "version": "1.3.10", 4 | "description": "BEM React ClassNames merge", 5 | "homepage": "https://github.com/bem/bem-react/tree/master/packages/classnames", 6 | "repository": "https://github.com/bem/bem-react", 7 | "keywords": ["classes", "merge"], 8 | "main": "index.cjs", 9 | "typings": "classnames.d.ts", 10 | "exports": { 11 | "development": { 12 | "require": "./build/classnames.development.cjs", 13 | "module": "./build/classnames.development.mjs" 14 | }, 15 | "production": { 16 | "require": "./build/classnames.production.min.cjs", 17 | "module": "./build/classnames.production.min.mjs" 18 | }, 19 | "require": "./index.cjs", 20 | "default": "./build/classnames.production.min.mjs" 21 | }, 22 | "publishConfig": { 23 | "access": "public" 24 | }, 25 | "files": ["build", "classnames.d.ts"], 26 | "license": "MPL-2.0", 27 | "scripts": { 28 | "prepublishOnly": "npm run build", 29 | "build": "node ../../scripts/rollup/build.js", 30 | "unit": "../../node_modules/.bin/jest --config ../../.config/jest/jest.config.js" 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /packages/classnames/test/classnames.test.ts: -------------------------------------------------------------------------------- 1 | import { classnames } from '../classnames' 2 | 3 | describe('@bem-react/classnames', () => { 4 | describe('classnames', () => { 5 | test('empty', () => { 6 | expect(classnames()).toEqual('') 7 | }) 8 | 9 | test('undefined', () => { 10 | expect(classnames('Block', undefined, 'Block2')).toEqual('Block Block2') 11 | }) 12 | 13 | test('uniq', () => { 14 | expect(classnames('CompositeBlock', 'Block', 'Test', 'Block')).toEqual( 15 | 'CompositeBlock Block Test', 16 | ) 17 | }) 18 | }) 19 | }) 20 | -------------------------------------------------------------------------------- /packages/classnames/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.json", 3 | "compilerOptions": { 4 | /* Basic Options */ 5 | "rootDir": ".", 6 | "declarationDir": ".", 7 | "outDir": "build" 8 | }, 9 | "include": ["../../environment.d.ts"] 10 | } 11 | -------------------------------------------------------------------------------- /packages/core/.gitignore: -------------------------------------------------------------------------------- 1 | core.d.ts 2 | -------------------------------------------------------------------------------- /packages/core/README.md: -------------------------------------------------------------------------------- 1 | # @bem-react/core · [![npm (scoped)](https://img.shields.io/npm/v/@bem-react/core.svg)](https://www.npmjs.com/package/@bem-react/core) [![npm bundle size (minified + gzip)](https://img.shields.io/bundlephobia/minzip/@bem-react/core.svg)](https://bundlephobia.com/result?p=@bem-react/core) 2 | 3 | Core package helps organize and manage components with [BEM modifiers](https://en.bem.info/methodology/key-concepts/#modifier) in React. 4 | 5 | ## Install 6 | 7 | ``` 8 | npm i -S @bem-react/core 9 | ``` 10 | 11 | ## Usage 12 | 13 | Let's say, you have an initial App file structure as follows: 14 | 15 | ``` 16 | App.tsx 17 | Components/ 18 | Button/ 19 | Button.tsx 20 | ``` 21 | 22 | And you need to set up two optional types of buttons that will be different from the `Button.tsx`. _(In our example those will be Button of theme 'action' and Button of type 'link')_ 23 | 24 | You can handle those using _@bem-react/core_. 25 | 26 | Follow the guide. 27 | 28 | #### Step 1. 29 | 30 | In your `Components/Button/index.tsx`, you define the type of props your button can get within the interface that extends **IClassNameProps** from '@bem-react/core' : 31 | 32 | ```ts 33 | import { ReactType } from 'react' 34 | import { IClassNameProps } from '@bem-react/core' 35 | import { cn } from '@bem-react/classname' 36 | 37 | export interface IButtonProps extends IClassNameProps { 38 | as?: ReactType 39 | } 40 | 41 | export const cnButton = cn('Button') 42 | ``` 43 | 44 | #### Step 2. 45 | 46 | Set up the **basic Button** variant which will be rendered if **no modifiers** props are set in the parent component. 47 | Inside your `Components/Button/Button.tsx`: 48 | 49 | ```tsx 50 | import React, { FC } from 'react' 51 | 52 | import { IButtonProps, cnButton } from './index' 53 | 54 | export const Button: FC = ({ 55 | children, 56 | className, 57 | as: Component = 'button', 58 | ...props 59 | }) => ( 60 | 61 | {children} 62 | 63 | ) 64 | ``` 65 | 66 | #### Step 3. 67 | 68 | Set up the **optional withButtonTypeLink** and **optional withButtonThemeAction** variants that will be rendered if `{type: 'link'}` and/or `{theme: 'action'}` modifiers are set in the parent component respectively. 69 | Inside your `Components/Button/` you add folders `_type/` with `Button_type_link.tsx` file in it and `_theme/` with `Button_theme_action.tsx` . 70 | 71 | ``` 72 | App.tsx 73 | Components/ 74 | Button/ 75 | Button.tsx 76 | index.tsx 77 | + _type/ 78 | + Button_type_link.tsx 79 | + _theme/ 80 | + Button_theme_action.tsx 81 | ``` 82 | 83 | Set up the variants: 84 | 85 | **Note!** The second parameter in `withBemMod()` is the condition for this component to be applied. 86 | 87 | **1.** In `Components/Button/_type/Button_type_link.tsx` 88 | 89 | ```tsx 90 | import React from 'react' 91 | import { withBemMod } from '@bem-react/core' 92 | 93 | import { IButtonProps, cnButton } from '../index' 94 | 95 | export interface IButtonTypeLinkProps { 96 | type?: 'link' 97 | href?: string 98 | } 99 | 100 | export const withButtonTypeLink = withBemMod( 101 | cnButton(), 102 | { type: 'link' }, 103 | (Button) => (props) => 149 | // Renders into HTML as: 150 | 151 | 152 | // Renders into HTML as: I'm type link 153 | 154 | 155 | // Renders into HTML as: 156 | 157 | 158 | // Renders into HTML as: I'm all together 159 | 160 | ); 161 | ``` 162 | 163 | **Note!** The order of optional components composed onto ButtonPresenter is important: in case you have different layouts and need to apply several modifiers the **FIRST** one inside the compose method will be rendered! 164 | E.g., here: 165 | 166 | ```tsx 167 | export const Button = compose( 168 | withButtonThemeAction, 169 | withButtonTypeLink, 170 | )(ButtonPresenter) 171 | ``` 172 | 173 | If your withButtonThemeAction was somewhat like 174 | 175 | `` 176 | 177 | your JSX-component: 178 | 179 | `` 180 | 181 | would render into HTML: 182 | 183 | `Hello` 184 | 185 | ## Use reexports for better DX 186 | 187 | > **IMPORTANT:** use this solution if [tree shaking](https://webpack.js.org/guides/tree-shaking/) enabled 188 | 189 | Example: 190 | 191 | ``` 192 | Block/Block.tsx 193 | Block/Block@desktop.tsx 194 | Block/_mod/Block_mod_val1.tsx 195 | Block/_mod/Block_mod_val2.tsx 196 | Block/_mod/Block_mod_val3.tsx 197 | ``` 198 | 199 | Create reexports for all modifiers in index files by platform: desktop, phone, amp, etc. 200 | 201 | ```ts 202 | // Block/index.ts 203 | export * from './Block' 204 | export * from './Block/_mod' 205 | 206 | // Block/desktop.ts 207 | export * from './Block@desktop' 208 | export * from './Block/_mod' 209 | 210 | // Block/phone.ts 211 | export * from './' // for feature if not created platform version 212 | 213 | // Block/_mod/index.ts 214 | export * from './Block_mod_val1.tsx' 215 | export * from './Block_mod_val2.tsx' 216 | export * from './Block_mod_val3.tsx' 217 | ``` 218 | 219 | Usage: 220 | 221 | ```ts 222 | // App.tsx 223 | import { Block as BlockPresenter, withModVal1 } from './components/Block/desktop' 224 | 225 | const Block = withModVal1(BlockPresenter) 226 | ``` 227 | 228 | ## Optimization. Lazy load for modifiers. 229 | 230 | Solution for better code splitting with React.lazy and dynamic imports 231 | 232 | > **NOTE** If your need SSR replace React.lazy method for load `Block_mod.async.tsx` module to [@loadable/components](https://www.smooth-code.com/open-source/loadable-components/) or [react-loadable](https://github.com/jamiebuilds/react-loadable) 233 | 234 | ```tsx 235 | // Block/_mod/Block_mod.async.tsx 236 | import React from 'react' 237 | import { cnBlock } from '../Block' 238 | 239 | import './Block_mod.css' 240 | 241 | export const DynamicPart: React.FC = () => Loaded dynamicly 242 | 243 | // default export needed for React.lazy 244 | export default DynamicPart 245 | ``` 246 | 247 | ```tsx 248 | // Block/_mod/Block_mod.tsx 249 | import React, { Suspense, lazy } from 'react' 250 | import { cnBlock } from '../Block' 251 | 252 | export interface BlockModProps { 253 | mod?: boolean 254 | } 255 | 256 | export const withMod = withBemMod(cnBlock(), { mod: true }, (Block) => (props) => { 257 | const DynamicPart = lazy(() => import('./Block_mod.async.tsx')) 258 | 259 | return ( 260 | Updating...}> 261 | 262 | 263 | 264 | 265 | ) 266 | }) 267 | ``` 268 | 269 | Usage: 270 | 271 | ```ts 272 | // App.tsx 273 | import { 274 | Block as BlockPresenter, 275 | withMod 276 | } from './components/Block/desktop'; 277 | 278 | const Block = withMod(BlockPresenter); 279 | 280 | export const App = () => { 281 | return ( 282 | {/* chunk with DynamicPart not loaded */} 283 | 284 | 285 | {/* chunk with DynamicPart loaded */} 286 | 287 | ); 288 | } 289 | ``` 290 | 291 | ## Simple modifiers (only CSS classes) 292 | 293 | In most cases you need change only CSS class. This mode doesn't pass modififer value to props. 294 | It doesn't create React wrappers for component, that's way more optimal. 295 | 296 | ```tsx 297 | import React from 'react' 298 | import { cnBlock } from '../Block' 299 | 300 | export interface BlockModProps { 301 | simplemod?: boolean 302 | } 303 | 304 | export const withSimpleMod = createClassNameModifier(cnBlock(), { simplemod: true }) 305 | ``` 306 | 307 | ## Debug 308 | 309 | To help your debug "@bem-react/core" support development mode. 310 | 311 | For `` (from the **Example** above), React DevTools will show: 312 | 313 | ```html 314 | 315 | 316 | Hello 317 | 318 | 319 | ``` 320 | -------------------------------------------------------------------------------- /packages/core/index.cjs: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | if (process.env.NODE_ENV === 'production') { 4 | module.exports = require('./build/core.production.min.cjs') 5 | } else { 6 | module.exports = require('./build/core.development.cjs') 7 | } 8 | -------------------------------------------------------------------------------- /packages/core/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@bem-react/core", 3 | "version": "5.1.0", 4 | "description": "BEM React Core", 5 | "homepage": "https://github.com/bem/bem-react/tree/master/packages/core", 6 | "repository": "https://github.com/bem/bem-react", 7 | "keywords": ["bem", "modifier", "withBemMod", "core"], 8 | "main": "index.cjs", 9 | "typings": "core.d.ts", 10 | "exports": { 11 | "development": { 12 | "require": "./build/core.development.cjs", 13 | "module": "./build/core.development.mjs" 14 | }, 15 | "production": { 16 | "require": "./build/core.production.min.cjs", 17 | "module": "./build/core.production.min.mjs" 18 | }, 19 | "require": "./index.cjs", 20 | "default": "./build/core.production.min.mjs" 21 | }, 22 | "publishConfig": { 23 | "access": "public" 24 | }, 25 | "files": ["build", "core.d.ts"], 26 | "license": "MPL-2.0", 27 | "scripts": { 28 | "prepublishOnly": "npm run build", 29 | "build": "node ../../scripts/rollup/build.js", 30 | "unit": "../../node_modules/.bin/jest --config ../../.config/jest/jest.config.js" 31 | }, 32 | "dependencies": { 33 | "@bem-react/classname": "1.6.0", 34 | "@bem-react/classnames": "1.3.10" 35 | }, 36 | "peerDependencies": { 37 | "react": "^16.8.0 || ^17.0.0 || ^18.0.0" 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /packages/core/test/compose.test.tsx: -------------------------------------------------------------------------------- 1 | import React, { FC, ComponentType, ReactNode } from 'react' 2 | import { render } from '@testing-library/react' 3 | 4 | import { compose, composeU, createClassNameModifier, withBemMod } from '../core' 5 | 6 | type BaseProps = { 7 | text: string 8 | children?: ReactNode 9 | } 10 | 11 | type HoveredProps = { 12 | hovered?: boolean 13 | } 14 | 15 | type ThemeAProps = { 16 | theme?: 'a' 17 | } 18 | 19 | type ThemeBProps = { 20 | theme?: 'b' 21 | } 22 | 23 | type SizeAProps = { 24 | size?: 'a' 25 | } 26 | 27 | type SimpleProps = { 28 | simple?: 'somevalue' | 'errorvalue' 29 | } 30 | 31 | type SimpleBooleanProps = { 32 | isBoolean?: boolean 33 | } 34 | 35 | function expectAttrs(Component: React.ReactElement, attrs: { [key: string]: string }) { 36 | const { container } = render(Component) 37 | const node = container.querySelector('*') 38 | for (const a in attrs) { 39 | expect(node?.getAttribute(a)).toEqual(attrs[a]) 40 | } 41 | } 42 | 43 | const Component: FC = ({ children }) =>
{children}
44 | const ComponentSpreadProps: FC = ({ children, ...props }) => { 45 | if ( 46 | // @ts-ignore 47 | (props.hasOwnProperty('isBoolean') && props.isBoolean === undefined) 48 | // @ts-ignore 49 | || props.isBoolean === false 50 | ) { 51 | throw Error('props must be omitted') 52 | } 53 | 54 | return
{children}
55 | } 56 | const withSimpleCompose = createClassNameModifier('EnhancedComponent', { 57 | simple: 'somevalue', 58 | }) 59 | 60 | const withSimpleBooleanCompose = createClassNameModifier('EnhancedComponent', { 61 | isBoolean: true, 62 | }) 63 | 64 | const withAutoSimpleCompose = withBemMod<{ autosimple?: 'yes' }>('EnhancedComponent', { 65 | autosimple: 'yes', 66 | }) 67 | 68 | const withHover = (Wrapped: ComponentType) => (props: HoveredProps) => 69 | const withThemeA = (Wrapped: ComponentType) => (props: ThemeAProps) => 70 | const withThemeB = (Wrapped: ComponentType) => (props: ThemeBProps) => 71 | const withSizeA = (Wrapped: ComponentType) => (props: SizeAProps) => 72 | 73 | const EnhancedComponent = compose( 74 | withSimpleCompose, 75 | withAutoSimpleCompose, 76 | withHover, 77 | withSizeA, 78 | composeU(withThemeA, withThemeB), 79 | )(Component) 80 | 81 | const EnhancedComponentRemoveProp = compose( 82 | withSimpleCompose, 83 | withSimpleBooleanCompose, 84 | withAutoSimpleCompose, 85 | withHover, 86 | withThemeB, 87 | )(ComponentSpreadProps) 88 | 89 | describe('compose', () => { 90 | test('should compile component with theme a', () => { 91 | render() 92 | }) 93 | 94 | test('should compile component with theme b', () => { 95 | render() 96 | }) 97 | 98 | test('should compile component with hovered true', () => { 99 | render() 100 | }) 101 | 102 | test('should compile component with simple mod', () => { 103 | render() 104 | }) 105 | 106 | test('remove mod props in simple mod', () => { 107 | expectAttrs( 108 | , 115 | { 116 | autosimple: 'yes', 117 | class: 118 | 'EnhancedComponent EnhancedComponent_simple_somevalue EnhancedComponent_isBoolean EnhancedComponent_autosimple_yes', 119 | text: '', 120 | theme: 'b', 121 | }, 122 | ) 123 | }) 124 | 125 | test('remove mod props in simple mod if boolean value', () => { 126 | expectAttrs(, { 127 | class: 'EnhancedComponent', 128 | text: '', 129 | }) 130 | }) 131 | 132 | test("don't remove mod props in simple mod if value hasn't matched", () => { 133 | expectAttrs( 134 | , 135 | { 136 | class: 'EnhancedComponent', 137 | simple: 'errorvalue', 138 | text: '', 139 | theme: 'b', 140 | }, 141 | ) 142 | }) 143 | }) 144 | -------------------------------------------------------------------------------- /packages/core/test/withBemMod.test.tsx: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react' 2 | import { render } from '@testing-library/react' 3 | import { cn } from '@bem-react/classname' 4 | 5 | import { withBemMod, IClassNameProps } from '../core' 6 | 7 | const getClassNameFromSelector = (Component: React.ReactElement, selector: string = 'div') => { 8 | const { container } = render(Component) 9 | return container.querySelector(selector)?.className 10 | } 11 | 12 | interface IPresenterProps extends IClassNameProps { 13 | theme?: 'normal' 14 | view?: 'default' 15 | url?: string 16 | } 17 | 18 | const presenter = cn('Presenter') 19 | 20 | const Presenter: React.FC = ({ className }) => ( 21 |
22 | ) 23 | 24 | describe('withBemMod', () => { 25 | test('should not affect CSS class with empty object', () => { 26 | const WBCM = withBemMod(presenter(), {})(Presenter) 27 | expect(getClassNameFromSelector()).toEqual( 28 | 'Presenter Additional', 29 | ) 30 | }) 31 | 32 | test('should add modifier class for matched prop', () => { 33 | const Enhanced1 = withBemMod(presenter(), { theme: 'normal' })(Presenter) 34 | const Enhanced2 = withBemMod(presenter(), { view: 'default' })(Enhanced1) 35 | const Component = 36 | 37 | expect(getClassNameFromSelector(Component)).toEqual( 38 | 'Presenter Presenter_theme_normal Presenter_view_default Additional', 39 | ) 40 | }) 41 | 42 | test('should not add modifier class for star matched prop', () => { 43 | const Enhanced1 = withBemMod(presenter(), { url: '*' })(Presenter) 44 | const Component = 45 | 46 | expect(getClassNameFromSelector(Component)).toEqual('Presenter Additional') 47 | }) 48 | 49 | test('should match on star matched prop', () => { 50 | const Enhanced1 = withBemMod(presenter(), { url: '*' }, (Base) => (props) => ( 51 | 52 | ))(Presenter) 53 | const Component = 54 | 55 | expect(getClassNameFromSelector(Component)).toEqual('Presenter Additional') 56 | }) 57 | 58 | test('should not add modifier class for unmatched prop', () => { 59 | const WBCM = withBemMod(presenter(), { theme: 'normal' })(Presenter) 60 | expect(getClassNameFromSelector()).toEqual( 61 | 'Presenter Additional', 62 | ) 63 | }) 64 | 65 | test('should not initialized after change props', () => { 66 | const init = jest.fn() 67 | const Enhanced = withBemMod( 68 | presenter(), 69 | { theme: 'normal' }, 70 | (WrappedComponent) => 71 | class WithEnhanced extends React.PureComponent { 72 | constructor(props: IPresenterProps) { 73 | super(props) 74 | init() 75 | } 76 | 77 | render() { 78 | return 79 | } 80 | }, 81 | )(Presenter) 82 | 83 | const { container } = render() 84 | render(, { container }) 85 | expect(init).toHaveBeenCalledTimes(1) 86 | }) 87 | 88 | test('should cache new component for every new call of `withBemMod` returned function', () => { 89 | const withTheme = withBemMod( 90 | presenter(), 91 | { theme: 'normal' }, 92 | (Base) => (props) => , 93 | ) 94 | const withView = withBemMod( 95 | presenter(), 96 | { view: 'default' }, 97 | (Base) => (props) => , 98 | ) 99 | 100 | const Enhanced1 = withTheme(Presenter) 101 | const Enhanced2 = withTheme(withView(Presenter)) 102 | 103 | render() 104 | expect(getClassNameFromSelector()).toEqual( 105 | 'Presenter Presenter_view_default Presenter_theme_normal', 106 | ) 107 | }) 108 | 109 | test('should forward ref', () => { 110 | class PresenterClass extends Component { 111 | render() { 112 | return
113 | } 114 | } 115 | 116 | const withTheme = withBemMod(presenter(), { theme: 'normal' }) 117 | 118 | // Unfortunately, manual type cast is necessary 119 | const Enhanced = withTheme(PresenterClass) as any as React.ForwardRefExoticComponent< 120 | IPresenterProps & { ref: React.Ref } 121 | > 122 | 123 | const ref = React.createRef() 124 | 125 | render() 126 | expect(ref.current).toBeInstanceOf(PresenterClass) 127 | }) 128 | }) 129 | -------------------------------------------------------------------------------- /packages/core/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.json", 3 | "compilerOptions": { 4 | /* Basic Options */ 5 | "rootDir": ".", 6 | "declarationDir": ".", 7 | "outDir": "build" 8 | }, 9 | "include": ["../../environment.d.ts"] 10 | } 11 | -------------------------------------------------------------------------------- /packages/di/.gitignore: -------------------------------------------------------------------------------- 1 | di.d.ts 2 | -------------------------------------------------------------------------------- /packages/di/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Change Log 2 | 3 | All notable changes to this project will be documented in this file. 4 | See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. 5 | 6 | # [5.0.0](https://github.com/bem/bem-react/compare/@bem-react/di@3.1.1...@bem-react/di@5.0.0) (2022-12-15) 7 | 8 | ### Bug Fixes 9 | 10 | - types ([17ab34b](https://github.com/bem/bem-react/commit/17ab34b168c5f5904b6a9b87c0fbb7f9a071cf0a)) 11 | 12 | ### Features 13 | 14 | - update to react@18 ([f08e4d6](https://github.com/bem/bem-react/commit/f08e4d686d7891e4356859932ee18812700a4e27)) 15 | 16 | # [4.0.0](https://github.com/bem/bem-react/compare/@bem-react/di@3.1.1...@bem-react/di@4.0.0) (2022-12-15) 17 | 18 | ### Bug Fixes 19 | 20 | - types ([17ab34b](https://github.com/bem/bem-react/commit/17ab34b168c5f5904b6a9b87c0fbb7f9a071cf0a)) 21 | 22 | ### Features 23 | 24 | - update to react@18 ([f08e4d6](https://github.com/bem/bem-react/commit/f08e4d686d7891e4356859932ee18812700a4e27)) 25 | 26 | ## [3.1.1](https://github.com/bem/bem-react/compare/@bem-react/di@3.1.0...@bem-react/di@3.1.1) (2021-06-29) 27 | 28 | ### Bug Fixes 29 | 30 | - **di:** fix partial merge of registries ([3ef511d](https://github.com/bem/bem-react/commit/3ef511dc18b01676d9528b62e71b81c9ca16d71d)) 31 | 32 | # [3.1.0](https://github.com/bem/bem-react/compare/@bem-react/di@3.0.0...@bem-react/di@3.1.0) (2021-06-23) 33 | 34 | ### Features 35 | 36 | - **di:** rename ComponentRegistryConsumer to RegistryConsumer ([2420041](https://github.com/bem/bem-react/commit/24200415e8b54868ab7932a9531e5313d316b526)) 37 | 38 | # [3.0.0](https://github.com/bem/bem-react/compare/@bem-react/di@2.2.8...@bem-react/di@3.0.0) (2021-06-22) 39 | 40 | ### Features 41 | 42 | - **di:** make di able to keep anything ([e302953](https://github.com/bem/bem-react/commit/e30295305e133ba240e5dc691eb80ab04199c12e)) 43 | 44 | ### BREAKING CHANGES 45 | 46 | - **di:** `HOC` and `IRegistryComponents` aren't exported from di, and generic-param for `Registry.set` has different meaning 47 | 48 | ## [2.2.8](https://github.com/bem/bem-react/compare/@bem-react/di@2.2.7...@bem-react/di@2.2.8) (2021-06-21) 49 | 50 | ### Bug Fixes 51 | 52 | - **di:** expose IRegistryComponents type ([d32a0a7](https://github.com/bem/bem-react/commit/d32a0a7e24f5082943a7b9d854f00a17437de8fe)) 53 | 54 | ## [2.2.7](https://github.com/bem/bem-react/compare/@bem-react/di@2.2.6...@bem-react/di@2.2.7) (2021-06-08) 55 | 56 | ### Bug Fixes 57 | 58 | - update pkg ([1ccdee8](https://github.com/bem/bem-react/commit/1ccdee8d9c4c09a02f888ee880a332ac75b725fd)) 59 | 60 | ## [2.2.6](https://github.com/bem/bem-react/compare/@bem-react/di@2.2.5...@bem-react/di@2.2.6) (2021-04-27) 61 | 62 | ### Bug Fixes 63 | 64 | - **di:** supports react@17 ([0c19c61](https://github.com/bem/bem-react/commit/0c19c6135a0fdbbf82dcb808f745b57320dbad76)) 65 | 66 | ## [2.2.5](https://github.com/bem/bem-react/compare/@bem-react/di@2.2.4...@bem-react/di@2.2.5) (2020-11-13) 67 | 68 | ### Bug Fixes 69 | 70 | - **di:** fixed merge of registries with same id ([0706c4a](https://github.com/bem/bem-react/commit/0706c4ad5117c3107df24d42abe8b67eebbec30c)) 71 | 72 | ## [2.2.4](https://github.com/bem/bem-react/compare/@bem-react/di@2.2.3...@bem-react/di@2.2.4) (2020-04-02) 73 | 74 | ### Bug Fixes 75 | 76 | - **di:** resolve [#551](https://github.com/bem/bem-react/issues/551) issue ([c4491a4](https://github.com/bem/bem-react/commit/c4491a44268bd61ec77316208b918c03abea65c8)) 77 | 78 | ## [2.2.3](https://github.com/bem/bem-react/compare/@bem-react/di@2.2.2...@bem-react/di@2.2.3) (2020-03-12) 79 | 80 | ### Performance Improvements 81 | 82 | - **di:** use for of loop instead forEach ([68d239c](https://github.com/bem/bem-react/commit/68d239c3f537a7203a9d8644a81ab4623fedb2eb)) 83 | - **di:** use native createElement instead jsx ([eb3ff64](https://github.com/bem/bem-react/commit/eb3ff6461a1eaa0558df0ab3aebf32a302a35a77)) 84 | 85 | ## [2.2.2](https://github.com/bem/bem-react/compare/@bem-react/di@2.2.1...@bem-react/di@2.2.2) (2020-03-02) 86 | 87 | ### Bug Fixes 88 | 89 | - **di:** fixes a way to extends components ([629b2a5](https://github.com/bem/bem-react/commit/629b2a508d92997b30a2f7a9342b2f0fd4337e4b)) 90 | 91 | ### Performance Improvements 92 | 93 | - **di:** improving performance ([469966f](https://github.com/bem/bem-react/commit/469966f01f4c11f27971625d8681f38beabcd773)) 94 | 95 | ## [2.2.1](https://github.com/bem/bem-react/compare/@bem-react/di@2.2.0...@bem-react/di@2.2.1) (2019-12-27) 96 | 97 | ### Bug Fixes 98 | 99 | - **di:** Add errors when base component is empty for extending component ([8595d01](https://github.com/bem/bem-react/commit/8595d01194ae00af4216a5d5824205c62d1e1161)) 100 | 101 | # [2.2.0](https://github.com/bem/bem-react/compare/@bem-react/di@2.1.0...@bem-react/di@2.2.0) (2019-12-23) 102 | 103 | ### Features 104 | 105 | - **di:** Add withBase-hoc as a way of extending components ([dda4d8b](https://github.com/bem/bem-react/commit/dda4d8b22325331a46e19dad75dae7da5a388aed)) 106 | 107 | # [2.1.0](https://github.com/bem/bem-react/compare/@bem-react/di@2.0.4...@bem-react/di@2.1.0) (2019-12-02) 108 | 109 | ### Features 110 | 111 | - **di:** fill registry with components via object literal ([a4f69c5](https://github.com/bem/bem-react/commit/a4f69c5c12e4bdf31c66994174d75fd82bc76674)) 112 | 113 | ## [2.0.4](https://github.com/bem/bem-react/compare/@bem-react/di@2.0.3...@bem-react/di@2.0.4) (2019-10-02) 114 | 115 | **Note:** Version bump only for package @bem-react/di 116 | 117 | ## [2.0.3](https://github.com/bem/bem-react/compare/@bem-react/di@2.0.2...@bem-react/di@2.0.3) (2019-08-20) 118 | 119 | **Note:** Version bump only for package @bem-react/di 120 | 121 | ## [2.0.2](https://github.com/bem/bem-react/compare/@bem-react/di@2.0.1...@bem-react/di@2.0.2) (2019-07-31) 122 | 123 | **Note:** Version bump only for package @bem-react/di 124 | 125 | ## [2.0.1](https://github.com/bem/bem-react/compare/@bem-react/di@2.0.0...@bem-react/di@2.0.1) (2019-05-27) 126 | 127 | ### Bug Fixes 128 | 129 | - **di:** return type in GetNonDefaultProps without GetNonDefaultProps ([9f3ab8e](https://github.com/bem/bem-react/commit/9f3ab8e)) 130 | 131 | # [2.0.0](https://github.com/bem/bem-react/tree/master/packages/di/compare/@bem-react/di@1.6.0...@bem-react/di@2.0.0) (2019-05-24) 132 | 133 | ### Features 134 | 135 | - **di:** replace inverted by overridable ([957a0fe](https://github.com/bem/bem-react/commit/957a0fe)) 136 | 137 | ### BREAKING CHANGES 138 | 139 | - **di:** Set inverted flag by default and rename it to "overridable". 140 | 141 | # [1.6.0](https://github.com/bem/bem-react/tree/master/packages/di/compare/@bem-react/di@1.5.3...@bem-react/di@1.6.0) (2019-04-22) 142 | 143 | ### Features 144 | 145 | - **di:** add hooks for registries and registryComponent ([c512dc2](https://github.com/bem/bem-react/commit/c512dc2)) 146 | 147 | ## [1.5.3](https://github.com/bem/bem-react/tree/master/packages/di/compare/@bem-react/di@1.5.2...@bem-react/di@1.5.3) (2019-03-01) 148 | 149 | ### Bug Fixes 150 | 151 | - **di:** registers are overwritten in context ([a7b6377](https://github.com/bem/bem-react/commit/a7b6377)) 152 | 153 | ## [1.5.2](https://github.com/bem/bem-react/tree/master/packages/di/compare/@bem-react/di@1.5.1...@bem-react/di@1.5.2) (2019-01-29) 154 | 155 | ### Bug Fixes 156 | 157 | - **di:** remove global variable providedRegistries ([8f5e93e](https://github.com/bem/bem-react/commit/8f5e93e)) 158 | 159 | ## [1.5.1](https://github.com/bem/bem-react/tree/master/packages/di/compare/@bem-react/di@1.5.0...@bem-react/di@1.5.1) (2019-01-16) 160 | 161 | ### Bug Fixes 162 | 163 | - **di:** provided registries must be global ([57fdb8b](https://github.com/bem/bem-react/commit/57fdb8b)) 164 | 165 | # [1.5.0](https://github.com/bem/bem-react/tree/master/packages/di/compare/@bem-react/di@1.4.0...@bem-react/di@1.5.0) (2019-01-10) 166 | 167 | ### Features 168 | 169 | - **di:** partially registries merge ([7890e03](https://github.com/bem/bem-react/commit/7890e03)) 170 | 171 | # [1.4.0](https://github.com/bem/bem-react/tree/master/packages/di/compare/@bem-react/di@1.3.0...@bem-react/di@1.4.0) (2018-12-28) 172 | 173 | ### Features 174 | 175 | - **di:** the way to add typings for registry result ([b76e4e1](https://github.com/bem/bem-react/commit/b76e4e1)) 176 | 177 | # 1.3.0 (2018-12-21) 178 | 179 | ### Bug Fixes 180 | 181 | - **di:** correct typings for withRegistry ([a79eca2](https://github.com/bem/bem-react/commit/a79eca2)) 182 | - **di:** return correct type from withRegistry ([e695088](https://github.com/bem/bem-react/commit/e695088)) 183 | - **di:** use map as class option for using in es5 ([24e9015](https://github.com/bem/bem-react/commit/24e9015)) 184 | 185 | ### Features 186 | 187 | - **v3:** init packages ([d652328](https://github.com/bem/bem-react/commit/d652328)) 188 | 189 | ## 1.2.2 (2018-12-21) 190 | 191 | ### Bug Fixes 192 | 193 | - **di:** correct typings for withRegistry ([a79eca2](https://github.com/bem/bem-react/commit/a79eca2)) 194 | - **di:** return correct type from withRegistry ([e695088](https://github.com/bem/bem-react/commit/e695088)) 195 | - **di:** use map as class option for using in es5 ([24e9015](https://github.com/bem/bem-react/commit/24e9015)) 196 | 197 | ### Features 198 | 199 | - **v3:** init packages ([d652328](https://github.com/bem/bem-react/commit/d652328)) 200 | 201 | ## 1.2.1 (2018-12-19) 202 | 203 | ### Bug Fixes 204 | 205 | - **di:** correct typings for withRegistry ([a79eca2](https://github.com/bem/bem-react/commit/a79eca2)) 206 | - **di:** return correct type from withRegistry ([4e09616](https://github.com/bem/bem-react/commit/4e09616)) 207 | - **di:** use map as class option for using in es5 ([24e9015](https://github.com/bem/bem-react/commit/24e9015)) 208 | 209 | ### Features 210 | 211 | - **v3:** init packages ([d652328](https://github.com/bem/bem-react/commit/d652328)) 212 | 213 | # 1.2.0 (2018-12-18) 214 | 215 | ### Bug Fixes 216 | 217 | - **di:** correct typings for withRegistry ([ce73d79](https://github.com/bem/bem-react/commit/ce73d79)) 218 | - **di:** use map as class option for using in es5 ([24e9015](https://github.com/bem/bem-react/commit/24e9015)) 219 | 220 | ### Features 221 | 222 | - **v3:** init packages ([d652328](https://github.com/bem/bem-react/commit/d652328)) 223 | 224 | # 1.1.0 (2018-12-06) 225 | 226 | ### Bug Fixes 227 | 228 | - **di:** use map as class option for using in es5 ([24e9015](https://github.com/bem/bem-react/commit/24e9015)) 229 | 230 | ### Features 231 | 232 | - **v3:** init packages ([d652328](https://github.com/bem/bem-react/commit/d652328)) 233 | 234 | 235 | 236 | ## [1.0.2](https://github.com/bem/bem-react-core/compare/@bem-react/di@1.0.1...@bem-react/di@1.0.2) (2018-10-24) 237 | 238 | **Note:** Version bump only for package @bem-react/di 239 | 240 | 241 | 242 | ## [1.0.1](https://github.com/bem/bem-react-core/compare/@bem-react/di@0.2.4...@bem-react/di@1.0.1) (2018-10-02) 243 | 244 | ### Bug Fixes 245 | 246 | - **di:** use map as class option for using in es5 ([7fd489f](https://github.com/bem/bem-react-core/commit/7fd489f)) 247 | 248 | 249 | 250 | # [1.0.0](https://github.com/bem/bem-react-core/compare/@bem-react/di@0.2.4...@bem-react/di@1.0.0) (2018-09-20) 251 | 252 | **Note:** Version bump only for package @bem-react/di 253 | 254 | 255 | 256 | ## [0.2.4](https://github.com/bem/bem-react-core/compare/@bem-react/di@0.2.3...@bem-react/di@0.2.4) (2018-09-13) 257 | 258 | **Note:** Version bump only for package @bem-react/di 259 | 260 | 261 | 262 | ## [0.2.3](https://github.com/bem/bem-react-core/compare/@bem-react/di@0.2.2...@bem-react/di@0.2.3) (2018-08-30) 263 | 264 | **Note:** Version bump only for package @bem-react/di 265 | 266 | 267 | 268 | ## [0.2.2](https://github.com/bem/bem-react-core/compare/@bem-react/di@0.2.1...@bem-react/di@0.2.2) (2018-08-29) 269 | 270 | **Note:** Version bump only for package @bem-react/di 271 | 272 | 273 | 274 | ## [0.2.1](https://github.com/bem/bem-react-core/compare/@bem-react/di@0.2.0...@bem-react/di@0.2.1) (2018-08-29) 275 | 276 | **Note:** Version bump only for package @bem-react/di 277 | 278 | 279 | 280 | # 0.2.0 (2018-08-29) 281 | 282 | ### Features 283 | 284 | - **v3:** init packages ([b192fc5](https://github.com/bem/bem-react-core/commit/b192fc5)) 285 | 286 | 287 | 288 | # 0.1.0 (2018-08-29) 289 | 290 | ### Features 291 | 292 | - **v3:** init packages ([b192fc5](https://github.com/bem/bem-react-core/commit/b192fc5)) 293 | -------------------------------------------------------------------------------- /packages/di/README.md: -------------------------------------------------------------------------------- 1 | # @bem-react/di · [![npm (scoped)](https://img.shields.io/npm/v/@bem-react/di.svg)](https://www.npmjs.com/package/@bem-react/di) [![npm bundle size (minified + gzip)](https://img.shields.io/bundlephobia/minzip/@bem-react/di.svg)](https://bundlephobia.com/result?p=@bem-react/di) 2 | 3 | **Dependency Injection (DI)** allows you to split React components into separate versions and comfortably switch them in the project whenever needed, e.g., to make a specific bundle. 4 | 5 | DI package helps to solve similar tasks with minimum effort: 6 | 7 | - decouple _desktop_ and _mobile_ versions of a component 8 | - implement an _experimental_ version of a component alongside the common one 9 | - store components and their auxiliaries (like settings and functions) in a single place 10 | 11 | ## Install 12 | 13 | ``` 14 | npm i -S @bem-react/di 15 | ``` 16 | 17 | ## Quick start 18 | 19 | **Note!** This example uses [@bem-react/classname package](https://github.com/bem/bem-react/tree/master/packages/classname). 20 | 21 | E.g., for a structure like this: 22 | 23 | ``` 24 | Components/ 25 | Header/ 26 | Header@desktop.tsx 27 | Header@mobile.tsx 28 | Footer/ 29 | Footer@desktop.tsx 30 | Footer@mobile.tsx 31 | App.tsx 32 | ``` 33 | 34 | First, create two files that define two versions of the App and use different sets of components: `App@desktop.tsx` and `App@mobile.tsx`. Put them near `App.tsx`. 35 | 36 | In each App version (`App@desktop.tsx` and `App@mobile.tsx`) we should define which components should be used. 37 | Three steps to do this: 38 | 39 | 1. Create a registry with a particular id: 40 | 41 | ```ts 42 | const registry = new Registry({ id: cnApp() }) 43 | ``` 44 | 45 | 2. Register all the needed components versions under a descriptive key (keys, describing similar components, should be the same across all the versions): 46 | 47 | ```ts 48 | registry.set('Header', Header) 49 | registry.set('Footer', Footer) 50 | ``` 51 | 52 | or 53 | 54 | ```ts 55 | registry.fill({ 56 | Header, 57 | Footer, 58 | }) 59 | ``` 60 | 61 | or 62 | 63 | ```ts 64 | registry.fill({ 65 | 'id-1': Header, 66 | 'id-2': Footer, 67 | }) 68 | ``` 69 | 70 | 3. Export the App version with its registry of components: 71 | 72 | ```ts 73 | export const AppNewVersion = withRegistry(registry)(AppCommon) 74 | ``` 75 | 76 | The files should look like this: 77 | 78 | **1.** In `App.tsx` 79 | 80 | ```tsx 81 | import { cn } from '@bem-react/classname' 82 | 83 | export const cnApp = cn('App') 84 | export const registryId = cnApp() 85 | ``` 86 | 87 | **2.** In `App@desktop.tsx` 88 | 89 | ```tsx 90 | import { Registry, withRegistry } from '@bem-react/di' 91 | import { App as AppCommon, registryId } from './App' 92 | 93 | import { Footer } from './Components/Footer/Footer@desktop' 94 | import { Header } from './Components/Header/Header@desktop' 95 | 96 | export const registry = new Registry({ id: registryId }) 97 | 98 | registry.set('Header', Header) 99 | registry.set('Footer', Footer) 100 | 101 | export const AppDesktop = withRegistry(registry)(AppCommon) 102 | ``` 103 | 104 | **3.** In `App@mobile.tsx` 105 | 106 | ```tsx 107 | import { Registry, withRegistry } from '@bem-react/di' 108 | import { App as AppCommon, registryId } from './App' 109 | 110 | import { Footer } from './Components/Footer/Footer@mobile' 111 | import { Header } from './Components/Header/Header@mobile' 112 | 113 | export const registry = new Registry({ id: registryId }) 114 | 115 | registry.set('Header', Header) 116 | registry.set('Footer', Footer) 117 | 118 | export const AppMobile = withRegistry(registry)(AppCommon) 119 | ``` 120 | 121 | Time to use these versions in your app dynamically! 122 | 123 | If in `App.tsx` your dependencies were static before 124 | 125 | ```tsx 126 | import React from 'react' 127 | import { cn } from '@bem-react/classname' 128 | import { Header } from './Components/Header/Header' 129 | import { Footer } from './Components/Footer/Footer' 130 | 131 | export const App = () => ( 132 | <> 133 |
134 |