-
Notifications
You must be signed in to change notification settings - Fork 0
/
index.ts
117 lines (96 loc) · 3.02 KB
/
index.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
interface States {
[stateName: string]: boolean | undefined; // if stateName start with $, we should refer it to a modifer
}
const ELEMENT_SIGN = '::';
const ELEMENT_SEP = '_';
const MODIFIER_SIGN = ':';
const MODIFIER_SEP = '--';
function _joint(Block: string, Element?: string, Modifier?: string): string {
if (!Block) {
return '';
}
let className = '';
if (Element && Modifier) {
className = `${Block}${ELEMENT_SEP}${Element}${MODIFIER_SEP}${Modifier}`;
} else if (Element && !Modifier) {
className = `${Block}${ELEMENT_SEP}${Element}`;
} else if (!Element && Modifier) {
className = `${Block}${MODIFIER_SEP}${Modifier}`;
}
return className;
}
/**
* BEM classname provider, esay to get a string of classes reflect current states,
* Note: State value must come from React.useState!
* @param blockname block name
* @return a method which generate a States reactive className string
*/
const BEMProvider = function(
blockname: string,
): (classString?: string | States, states?: States) => { className: string } {
const Block = blockname;
let Element: string = '';
let Modifier: string = '';
function $BEM(): string {
return _joint(Block, Element, Modifier);
}
// TODO: list rules by comment
return function(classString, states) {
const result: string[] = [];
function $walkStates(obj: { [key: string]: any }) {
return function(stateName: string) {
let cls = '';
if (obj[stateName]) {
if (stateName.startsWith('$')) {
Modifier = stateName.slice(1);
cls = $BEM();
} else {
cls = `is-${stateName}`;
}
result.push(cls);
}
};
}
if (classString && typeof classString === 'object') {
// only has states or modifiers
result.push(Block);
Object.keys(classString).forEach($walkStates(classString));
} else if (typeof classString === 'string') {
// has extra classNames
const classList = classString.split(' ');
const isElement = classList[0].startsWith(ELEMENT_SIGN);
if (isElement) {
Element = classList[0].slice(ELEMENT_SIGN.length);
}
classList.forEach(cls => {
if (cls.startsWith(ELEMENT_SIGN)) {
// Block_Element
const lastElement = Element;
Element = cls.slice(ELEMENT_SIGN.length);
cls = $BEM();
Element = lastElement;
} else if (cls.startsWith(MODIFIER_SIGN)) {
// Block--Modifier
Modifier = cls.slice(MODIFIER_SIGN.length);
cls = $BEM();
} else if (cls === '$B') {
cls = Block;
}
result.push(cls);
});
if (states) {
const stateNameList = Object.keys(states);
if (stateNameList.length) {
stateNameList.forEach($walkStates(states));
}
}
} else if (!classString && !states) {
result.push(Block);
}
// reset
Modifier = '';
Element = '';
return { className: result.join(' ') };
};
};
export default BEMProvider;