Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[feature]: introduce BEM utility #477

Open
wants to merge 17 commits into
base: next
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/brown-rocks-wonder.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@soramitsu-ui/ui': patch
---

**refactor**(`SButton`): refactor BEM usage
5 changes: 5 additions & 0 deletions .changeset/dirty-peaches-cry.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@soramitsu-ui/ui': patch
---

**refactor**: move `type-fest` to prod dependencies and update it to `3.1.0`
5 changes: 5 additions & 0 deletions .changeset/shaggy-donkeys-swim.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@soramitsu-ui/ui': patch
---

**chore**: add `@soramitsu-ui/bem` to deps
1 change: 1 addition & 0 deletions Jenkinsfile
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ def pipeline = new org.js.LibPipeline(
buildDockerImage: 'build-tools/node:14-ubuntu-cypress',
npmLoginEmail: '[email protected]',
dockerImageName: 'soramitsu/soramitsu-js-ui-library',
preBuildCmds: ['npm install -g n','n 16.17.0', 'n prune', "yarn install"],
testCmds: ['yarn test:all'],
pushCmds: ['yarn publish-workspaces --no-verify-access'],
libPushBranches: ['master', 'next'],
Expand Down
24 changes: 13 additions & 11 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,15 @@
"scripts": {
"sb:serve": "yarn --cwd packages/ui sb:serve",
"sb:build": "yarn --cwd packages/ui sb:build",
"test:all": "run-s lint:check test:theme:unit build:vite-plugin-svg test:ui:unit build:theme test:ui:cy build:ui:only-vite test:ui:after-build",
"test:all": "run-s lint:check test:bem:unit test:theme:unit build:bem build:vite-plugin-svg test:ui:unit build:theme test:ui:cy build:ui:only-vite test:ui:after-build",
"test:theme:unit": "yarn --cwd packages/theme test",
"test:bem:unit": "yarn --cwd packages/bem test",
"test:ui:unit": "yarn --cwd packages/ui test:unit",
"test:ui:cy": "yarn --cwd packages/ui cy:ci:component",
"test:ui:after-build": "yarn --cwd packages/ui test:after-build",
"build": "run-s build:theme build:vite-plugin-svg build:ui",
"build": "run-s build:bem build:theme build:vite-plugin-svg build:ui",
"build:theme": "yarn --cwd packages/theme build",
"build:bem": "yarn --cwd packages/bem build",
"build:vite-plugin-svg": "yarn --cwd packages/vite-plugin-svg build",
"build:ui": "yarn --cwd packages/ui build",
"build:ui:only-vite": "yarn --cwd packages/ui build:vite",
Expand All @@ -28,19 +30,19 @@
"devDependencies": {
"@changesets/cli": "^2.17.0",
"@types/node": "^17.0.14",
"@typescript-eslint/eslint-plugin": "^5.12.1",
"@typescript-eslint/parser": "^5.12.1",
"@typescript-eslint/eslint-plugin": "^5.40.1",
"@typescript-eslint/parser": "^5.40.1",
"esbuild-jest": "^0.5.0",
"eslint": "^8.16.0",
"eslint-config-alloy": "^4.5.1",
"eslint": "^8.25.0",
"eslint-config-alloy": "^4.7.0",
"eslint-plugin-cypress": "^2.12.1",
"eslint-plugin-vue": "^9.0.1",
"eslint-plugin-vuejs-accessibility": "^1.1.1",
"eslint-plugin-vue": "^9.6.0",
"eslint-plugin-vuejs-accessibility": "^1.2.0",
"lerna": "^4.0.0",
"npm-run-all": "^4.1.5",
"prettier": "^2.6.2",
"prettier-eslint": "^15.0.0",
"prettier": "^2.7.1",
"prettier-eslint": "^15.0.1",
"prettier-eslint-cli": "^6.0.1",
"typescript": "4.6.4"
"typescript": "4.8.4"
}
}
154 changes: 154 additions & 0 deletions packages/bem/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,154 @@
# @soramitsu-ui/bem

Type-level [BEM](https://en.bem.info/methodology/naming-convention/) notation.

## Features

- Statically typed BEM schema
- Less boilerplate - no need to repeat root block name
- Less possibility to make a typo
- Support of classic BEM style (`block__elem_mod-name_mod-key`) and two-dashes (`block__elem--mod-name--mod-key`)

## Example

### Classic style

```ts
const bem = defineBem('v-btn')
// Block modifiers
.mod('loading')
.mod('show-icon')
.mod('icon-size', ['small', 'very-small'] as const)
.mod('icon-size', 'little')
// Block elements
.elem('spinner')
.elem('left-icon', (el) =>
el
// Element modifiers
.mod('active')
.mod('has-stroke')
.mod('right-span', ['big', 'very-big'] as const)
.mod('right-span', 'huge'),
)
.build()

type test = Expect<
Equal<
typeof bem,
{
_: 'v-btn'
'_icon-size_little': 'v-btn_icon-size_little'
'_icon-size_small': 'v-btn_icon-size_small'
'_icon-size_very-small': 'v-btn_icon-size_very-small'
_iconSize_little: 'v-btn_icon-size_little'
_iconSize_small: 'v-btn_icon-size_small'
_iconSize_verySmall: 'v-btn_icon-size_very-small'
_loading: 'v-btn_loading'
'_show-icon': 'v-btn_show-icon'
_showIcon: 'v-btn_show-icon'
'left-icon': 'v-btn__left-icon'
'left-icon_active': 'v-btn__left-icon_active'
'left-icon_has-stroke': 'v-btn__left-icon_has-stroke'
'left-icon_right-span_big': 'v-btn__left-icon_right-span_big'
'left-icon_right-span_huge': 'v-btn__left-icon_right-span_huge'
'left-icon_right-span_very-big': 'v-btn__left-icon_right-span_very-big'
leftIcon: 'v-btn__left-icon'
leftIcon_active: 'v-btn__left-icon_active'
leftIcon_hasStroke: 'v-btn__left-icon_has-stroke'
leftIcon_rightSpan_big: 'v-btn__left-icon_right-span_big'
leftIcon_rightSpan_huge: 'v-btn__left-icon_right-span_huge'
leftIcon_rightSpan_veryBig: 'v-btn__left-icon_right-span_very-big'
spinner: 'v-btn__spinner'
}
>
>
```

### Two-dashes style

```ts
const bem = defineBem('v-btn')
.mod('loading')
.mod('show-icon')
.mod('icon-size', ['small', 'very-small'] as const)
.mod('icon-size', 'little')
.elem('spinner')
.elem('left-icon', (el) =>
el
//
.mod('active')
.mod('has-stroke')
.mod('right-span', ['big', 'very-big'] as const)
.mod('right-span', 'huge'),
)
.build('two-dashes')

type test = Expect<
Equal<
typeof bem,
{
_: 'v-btn'
'_icon-size_little': 'v-btn--icon-size--little'
'_icon-size_small': 'v-btn--icon-size--small'
'_icon-size_very-small': 'v-btn--icon-size--very-small'
_iconSize_little: 'v-btn--icon-size--little'
_iconSize_small: 'v-btn--icon-size--small'
_iconSize_verySmall: 'v-btn--icon-size--very-small'
_loading: 'v-btn--loading'
'_show-icon': 'v-btn--show-icon'
_showIcon: 'v-btn--show-icon'
'left-icon': 'v-btn__left-icon'
'left-icon_active': 'v-btn__left-icon--active'
'left-icon_has-stroke': 'v-btn__left-icon--has-stroke'
'left-icon_right-span_big': 'v-btn__left-icon--right-span--big'
'left-icon_right-span_huge': 'v-btn__left-icon--right-span--huge'
'left-icon_right-span_very-big': 'v-btn__left-icon--right-span--very-big'
leftIcon: 'v-btn__left-icon'
leftIcon_active: 'v-btn__left-icon--active'
leftIcon_hasStroke: 'v-btn__left-icon--has-stroke'
leftIcon_rightSpan_big: 'v-btn__left-icon--right-span--big'
leftIcon_rightSpan_huge: 'v-btn__left-icon--right-span--huge'
leftIcon_rightSpan_veryBig: 'v-btn__left-icon--right-span--very-big'
spinner: 'v-btn__spinner'
}
>
>
```

## Install

```bash
npm i @soramitsu-ui/bem
```

## Questions

### Why keys are duplicated in both `camelCase` and `kebab-case`?

`camelCase` is used to make access shorter:

```ts
// shorter
bem.leftIcon_active

// longer
bem['left-icon_active']
```

However, in some cases we need access a key dynamically with some value:

```ts
const bem = defineBem('tooltip')
.mod('position', ['left-bottom', 'right-top'] as const)
.build()

function getClassByPosition(value: 'left-bottom' | 'right-top') {
return bem[`_position_${value}`]
}
```

Without preserving the initial case of `position` modifier, we wouldn't be able to implement `getClassByPosition()` so easy.

### Well, mostly I don't need to preserve keys case. How to avoid extra generation?

For now the implementation is naive and not focused on such an optimisation. However, it might be possible to be optimised with `Proxy` and lazy-keys-computation under the hood. But it is a question if it is possible to implement this without even bigger performance overhead.
34 changes: 34 additions & 0 deletions packages/bem/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
{
"name": "@soramitsu-ui/bem",
"version": "1.0.0",
"description": "Type-level BEM notation",
"type": "module",
"exports": {
".": {
"import": "./dist/lib.mjs",
"require": "./dist/lib.cjs",
"types": "./dist/lib.d.ts"
}
},
"main": "./dist/lib.cjs",
"types": "./dist/lib.d.ts",
"files": [
"dist"
],
"license": "Apache-2.0",
"publishConfig": {
"access": "public"
},
"dependencies": {
"fast-case": "^1.7.0",
"type-fest": "^3.1.0"
},
"devDependencies": {
"unbuild": "^0.9.4",
"vitest": "^0.24.3"
},
"scripts": {
"build": "unbuild",
"test": "vitest run"
}
}
Loading