-
Notifications
You must be signed in to change notification settings - Fork 64
/
Copy pathclassname.ts
161 lines (143 loc) · 3.87 KB
/
classname.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
/**
* List of classname.
*/
export type ClassNameList = string | Array<string | undefined>
/**
* Allowed modifiers format.
*
* @see https://en.bem.info/methodology/key-concepts/#modifier
*/
export type NoStrictEntityMods = Record<string, string | boolean | number | undefined>
/**
* BEM Entity className initializer.
*/
export type ClassNameInitilizer = (blockName: string, elemName?: string) => ClassNameFormatter
/**
* BEM Entity className formatter.
*/
export interface ClassNameFormatter {
(): string
(mods?: NoStrictEntityMods | null, mix?: ClassNameList): string
(elemName: string, elemMix?: ClassNameList): string
(elemName: string, elemMods?: NoStrictEntityMods | null, elemMix?: ClassNameList): string
}
/**
* Settings for the naming convention.
*/
export type Preset = {
/**
* Global namespace.
*
* @example `omg-Bem-Elem_mod_val`
*/
n?: string
/**
* Elem's delimiter.
*/
e?: string
/**
* Modifier's delimiter.
*/
m?: string
/**
* Modifier value delimiter.
*/
v?: string
}
function isEmpty(val: string | boolean | number | undefined) {
return !val && val !== 0
}
/**
* BEM className configure function.
*
* @example
* ``` ts
*
* import { withNaming } from '@bem-react/classname';
*
* const cn = withNaming({ n: 'ns-', e: '__', m: '_' });
*
* cn('block', 'elem'); // 'ns-block__elem'
* ```
*
* @param preset settings for the naming convention
*/
export function withNaming(preset: Preset): ClassNameInitilizer {
const nameSpace = preset.n || ''
const modValueDelimiter = preset.v || preset.m
function stringify(b: string, e?: string, m?: NoStrictEntityMods | null, mix?: ClassNameList) {
const entityName = e ? nameSpace + b + preset.e + e : nameSpace + b
let className = entityName
if (m) {
const modPrefix = ' ' + className + preset.m
for (const k in m) {
if (m.hasOwnProperty(k)) {
const modVal = m[k]
if (modVal === true) {
className += modPrefix + k
} else if (!isEmpty(modVal)) {
className += modPrefix + k + modValueDelimiter + modVal
}
}
}
}
if (mix !== undefined) {
mix = Array.isArray(mix) ? mix : [mix]
for (let i = 0, len = mix.length; i < len; i++) {
const value = mix[i]
// Skipping non-string values and empty strings
if (!value || typeof value.valueOf() !== 'string') continue
const mixes = value.valueOf().split(' ')
for (let j = 0; j < mixes.length; j++) {
const val = mixes[j]
if (val !== entityName) {
className += ' ' + val
}
}
}
}
return className
}
return function cnGenerator(b: string, e?: string): ClassNameFormatter {
return (
elemOrMods?: NoStrictEntityMods | string | null,
elemModsOrBlockMix?: NoStrictEntityMods | ClassNameList | null,
elemMix?: ClassNameList,
) => {
if (typeof elemOrMods === 'string') {
if (typeof elemModsOrBlockMix === 'string' || Array.isArray(elemModsOrBlockMix)) {
return stringify(b, elemOrMods, undefined, elemModsOrBlockMix)
}
return stringify(b, elemOrMods, elemModsOrBlockMix, elemMix)
}
return stringify(b, e, elemOrMods, elemModsOrBlockMix as ClassNameList)
}
}
}
/**
* BEM Entity className initializer with React naming preset.
*
* @example
* ``` ts
*
* import { cn } from '@bem-react/classname';
*
* const cat = cn('Cat');
*
* cat(); // Cat
* cat({ size: 'm' }); // Cat_size_m
* cat('Tail'); // Cat-Tail
* cat('Tail', { length: 'small' }); // Cat-Tail_length_small
*
* const dogPaw = cn('Dog', 'Paw');
*
* dogPaw(); // Dog-Paw
* dogPaw({ color: 'black', exists: true }); // Dog-Paw_color_black Dog-Paw_exists
* ```
*
* @see https://en.bem.info/methodology/naming-convention/#react-style
*/
export const cn = withNaming({
e: '-',
m: '_',
})