From cd132ea84eff3a1f0a7016a6e03514157280d3de Mon Sep 17 00:00:00 2001 From: 0x009922 Date: Fri, 21 Oct 2022 13:11:56 +0800 Subject: [PATCH 01/14] [feature]: introduce BEM utility Also bump TypeScript and `type-fest` versions --- .changeset/dirty-peaches-cry.md | 5 + package.json | 24 +- packages/bem/README.md | 98 +++ packages/bem/package.json | 34 + packages/bem/src/lib.spec.ts | 129 +++ packages/bem/src/lib.ts | 8 + packages/bem/src/types.ts | 246 ++++++ packages/bem/src/unchecked-builder.ts | 108 +++ packages/theme/package.json | 4 +- packages/ui/package.json | 8 +- yarn.lock | 1036 ++++++++++++++++++++----- 11 files changed, 1499 insertions(+), 201 deletions(-) create mode 100644 .changeset/dirty-peaches-cry.md create mode 100644 packages/bem/README.md create mode 100644 packages/bem/package.json create mode 100644 packages/bem/src/lib.spec.ts create mode 100644 packages/bem/src/lib.ts create mode 100644 packages/bem/src/types.ts create mode 100644 packages/bem/src/unchecked-builder.ts diff --git a/.changeset/dirty-peaches-cry.md b/.changeset/dirty-peaches-cry.md new file mode 100644 index 00000000..a9dcbd5c --- /dev/null +++ b/.changeset/dirty-peaches-cry.md @@ -0,0 +1,5 @@ +--- +'@soramitsu-ui/ui': patch +--- + +**refactor**: move `type-fest` to prod dependencies and update it to `3.1.0` diff --git a/package.json b/package.json index dfc93ea6..f0d6b133 100755 --- a/package.json +++ b/package.json @@ -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: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", @@ -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" } } diff --git a/packages/bem/README.md b/packages/bem/README.md new file mode 100644 index 00000000..4bb53e4b --- /dev/null +++ b/packages/bem/README.md @@ -0,0 +1,98 @@ +# @soramitsu-ui/bem + +Type-level BEM notation. + +## Features + +- Statically typed BEM schema +- Less boilerplate - no need to repeat root block name +- Less possibility to make a typo +- Supported BEM styles - classic (`block__elem_mod-name_mod-key`), 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') + .mod('icon-size', 'very-small') + // Block elements + .elem('spinner') + .elem('left-icon', (el) => + el + // Element modifiers + .mod('active') + .mod('has-stroke') + .mod('right-span', 'big') + .mod('right-span', 'very-big'), + ) + .build() + +type test = Expect< + Equal< + typeof bem, + { + block: 'v-btn' + block_loading: 'v-btn_loading' + block_showIcon: 'v-btn_show-icon' + block_iconSize_small: 'v-btn_icon-size_small' + block_iconSize_verySmall: 'v-btn_icon-size_very-small' + block__spinner: 'v-btn__spinner' + block__leftIcon: 'v-btn__left-icon' + block__leftIcon_active: 'v-btn__left-icon_active' + block__leftIcon_hasStroke: 'v-btn__left-icon_has-stroke' + block__leftIcon_rightSpan_big: 'v-btn__left-icon_right-span_big' + block__leftIcon_rightSpan_veryBig: 'v-btn__left-icon_right-span_very-big' + } + > +> +``` + +### Two-dashes style + +```ts +const bem = defineBem('v-btn') + .mod('loading') + .mod('show-icon') + .mod('icon-size', 'small') + .mod('icon-size', 'very-small') + .elem('spinner') + .elem('left-icon', (el) => + el + // + .mod('active') + .mod('has-stroke') + .mod('right-span', 'big') + .mod('right-span', 'very-big'), + ) + .build('two-dashes') + +type test = Expect< + Equal< + typeof bem, + { + block: 'v-btn' + block_loading: 'v-btn--loading' + block_showIcon: 'v-btn--show-icon' + block_iconSize_small: 'v-btn--icon-size--small' + block_iconSize_verySmall: 'v-btn--icon-size--very-small' + block__spinner: 'v-btn__spinner' + block__leftIcon: 'v-btn__left-icon' + block__leftIcon_active: 'v-btn__left-icon--active' + block__leftIcon_hasStroke: 'v-btn__left-icon--has-stroke' + block__leftIcon_rightSpan_big: 'v-btn__left-icon--right-span--big' + block__leftIcon_rightSpan_veryBig: 'v-btn__left-icon--right-span--very-big' + } + > +> +``` + +## Install + +```bash +npm i @soramitsu-ui/bem +``` diff --git a/packages/bem/package.json b/packages/bem/package.json new file mode 100644 index 00000000..e9c1ae38 --- /dev/null +++ b/packages/bem/package.json @@ -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" + } +} diff --git a/packages/bem/src/lib.spec.ts b/packages/bem/src/lib.spec.ts new file mode 100644 index 00000000..1ebf0492 --- /dev/null +++ b/packages/bem/src/lib.spec.ts @@ -0,0 +1,129 @@ +import { test, expect, describe } from 'vitest' +import { defineBem } from './lib' +import { Expect, Equal } from './types' + +describe('defineBem', () => { + const complexBem = defineBem('v-btn') + // Block modifiers + .mod('loading') + .mod('show-icon') + .mod('icon-size', 'small') + .mod('icon-size', 'very-small') + // Block elements + .elem('spinner') + .elem('left-icon', (el) => + el + // Element modifiers + .mod('active') + .mod('has-stroke') + .mod('right-span', 'big') + .mod('right-span', 'very-big'), + ) + + test('Only block', () => { + const bem = defineBem('s-table').build() + + type test = Expect> + + expect(test).toMatchInlineSnapshot('[Function]') + }) + + test('Simple block', () => { + const bem = defineBem('block').elem('elem').build() + + type test = Expect< + Equal< + typeof bem, + { + block: 'block' + block__elem: 'block__elem' + } + > + > + + expect(bem).toMatchInlineSnapshot(` + { + "block": "block", + "block__elem": "block__elem", + } + `) + }) + + test('Complex classic build', () => { + const bem = complexBem.build() + + type test = Expect< + Equal< + typeof bem, + { + block: 'v-btn' + block_loading: 'v-btn_loading' + block_showIcon: 'v-btn_show-icon' + block_iconSize_small: 'v-btn_icon-size_small' + block_iconSize_verySmall: 'v-btn_icon-size_very-small' + block__spinner: 'v-btn__spinner' + block__leftIcon: 'v-btn__left-icon' + block__leftIcon_active: 'v-btn__left-icon_active' + block__leftIcon_hasStroke: 'v-btn__left-icon_has-stroke' + block__leftIcon_rightSpan_big: 'v-btn__left-icon_right-span_big' + block__leftIcon_rightSpan_veryBig: 'v-btn__left-icon_right-span_very-big' + } + > + > + + expect(bem).toMatchInlineSnapshot(` + { + "block": "v-btn", + "block__leftIcon": "v-btn__left-icon", + "block__leftIcon_active": "v-btn__left-icon_active", + "block__leftIcon_hasStroke": "v-btn__left-icon_has-stroke", + "block__leftIcon_rightSpan_big": "v-btn__left-icon_right-span_big", + "block__leftIcon_rightSpan_veryBig": "v-btn__left-icon_right-span_very-big", + "block__spinner": "v-btn__spinner", + "block_iconSize_small": "v-btn_icon-size_small", + "block_iconSize_verySmall": "v-btn_icon-size_very-small", + "block_loading": "v-btn_loading", + "block_showIcon": "v-btn_show-icon", + } + `) + }) + + test('Complex two-dashes build', () => { + const bem = complexBem.build('two-dashes') + + type test = Expect< + Equal< + typeof bem, + { + block: 'v-btn' + block_loading: 'v-btn--loading' + block_showIcon: 'v-btn--show-icon' + block_iconSize_small: 'v-btn--icon-size--small' + block_iconSize_verySmall: 'v-btn--icon-size--very-small' + block__spinner: 'v-btn__spinner' + block__leftIcon: 'v-btn__left-icon' + block__leftIcon_active: 'v-btn__left-icon--active' + block__leftIcon_hasStroke: 'v-btn__left-icon--has-stroke' + block__leftIcon_rightSpan_big: 'v-btn__left-icon--right-span--big' + block__leftIcon_rightSpan_veryBig: 'v-btn__left-icon--right-span--very-big' + } + > + > + + expect(bem).toMatchInlineSnapshot(` + { + "block": "v-btn", + "block__leftIcon": "v-btn__left-icon", + "block__leftIcon_active": "v-btn__left-icon--active", + "block__leftIcon_hasStroke": "v-btn__left-icon--has-stroke", + "block__leftIcon_rightSpan_big": "v-btn__left-icon--right-span--big", + "block__leftIcon_rightSpan_veryBig": "v-btn__left-icon--right-span--very-big", + "block__spinner": "v-btn__spinner", + "block_iconSize_small": "v-btn--icon-size--small", + "block_iconSize_verySmall": "v-btn--icon-size--very-small", + "block_loading": "v-btn--loading", + "block_showIcon": "v-btn--show-icon", + } + `) + }) +}) diff --git a/packages/bem/src/lib.ts b/packages/bem/src/lib.ts new file mode 100644 index 00000000..a32f9940 --- /dev/null +++ b/packages/bem/src/lib.ts @@ -0,0 +1,8 @@ +import type { BlockBuilder } from './types' +import { BemBlock } from './unchecked-builder' + +export const defineBem: (blockName: r) => BlockBuilder = (blockName) => { + return new BemBlock(blockName) as unknown as BlockBuilder +} + +export type { BlockBuilder, BemStyle } from './types' diff --git a/packages/bem/src/types.ts b/packages/bem/src/types.ts new file mode 100644 index 00000000..f0af6f55 --- /dev/null +++ b/packages/bem/src/types.ts @@ -0,0 +1,246 @@ +import type { Simplify, CamelCase } from 'type-fest' + +export type Equal = (() => T extends X ? 1 : 2) extends () => T extends Y ? 1 : 2 ? true : false + +export type Expect = T + +export type BemStyle = 'classic' | 'two-dashes' + +export const BLOCK_KEY = 'block' as const + +export type RootBlockKey = typeof BLOCK_KEY + +type ApplyStyleToModifier