Skip to content

Commit 7bc69ee

Browse files
committed
feat: add :prop(condition) syntax
1 parent 7d17746 commit 7bc69ee

File tree

5 files changed

+131
-12
lines changed

5 files changed

+131
-12
lines changed

package.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,9 @@
4545
"homepage": "https://github.com/andreypopp/react-css-components#readme",
4646
"dependencies": {
4747
"babel-plugin-ast-literal": "^0.4.0",
48+
"babel-traverse": "^6.7.6",
4849
"babel-types": "^6.7.2",
50+
"babylon": "^6.7.0",
4951
"invariant": "^2.2.1",
5052
"loader-utils": "^0.2.14",
5153
"postcss": "^5.0.19",
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
.Label__prop__fe61dd {
2+
color: red
3+
}
4+
.Label__prop__7fceb6 {
5+
color: white
6+
}
7+
.Label__prop__d033ce {
8+
color: yellow
9+
}
10+
.Label__prop__29d2fe {
11+
color: black
12+
}
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
import React from "react";
2+
import styles from "css";
3+
export function Label({
4+
variant,
5+
...props }) {
6+
let className = styles.Label + (props.level == 1 && props.level < 1 ? ' ' + styles.Label__prop__fe61dd : '') + (props.mode[0] == 1 ? ' ' + styles.Label__prop__7fceb6 : '') + (props.callback() ? ' ' + styles.Label__prop__d033ce : '') + (props.indicies.some(x => x.ok) ? ' ' + styles.Label__prop__29d2fe : '');
7+
return React.createElement("div", { ...props, className
8+
});
9+
}
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
Label:prop(level == 1 && level < 1) {
2+
color: red;
3+
}
4+
Label:prop(mode[0] == 1) {
5+
color: white;
6+
}
7+
Label:prop(callback()) {
8+
color: yellow;
9+
}
10+
Label:prop(indicies.some(x => x.ok)) {
11+
color: black;
12+
}

src/index.js

Lines changed: 96 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,15 @@
33
* @flow
44
*/
55

6+
import {createHash} from 'crypto';
67
import invariant from 'invariant';
78
import * as LoaderUtils from 'loader-utils';
89

910
import {identifier, stringLiteral, program} from 'babel-types';
1011
import generate from 'babel-generator';
12+
import traverse from 'babel-traverse';
13+
import * as types from 'babel-types';
14+
import {parse} from 'babylon';
1115

1216
import * as postcss from 'postcss';
1317
import createSelectorParser, {className} from 'postcss-selector-parser';
@@ -26,6 +30,7 @@ type CSSNode = Object;
2630
type VariantSpec = {
2731
componentName: string;
2832
variantName: string;
33+
expression: ?string;
2934
};
3035

3136
type ComponentSpec = {
@@ -41,11 +46,55 @@ const LOADER = require.resolve('../webpack');
4146

4247
const COMPONENT_RE = /^[A-Z][a-zA-Z_0-9]*$/;
4348

49+
const PROP_VARIANT_NAME = ':prop';
50+
51+
function hash(value) {
52+
let hasher = createHash('md5');
53+
hasher.update(value);
54+
return hasher.digest('hex');
55+
}
56+
4457
function parseSelector(selector) {
4558
let parser = createSelectorParser();
4659
return parser.process(selector).res;
4760
}
4861

62+
function isPropReference(path) {
63+
if (!types.isIdentifier(path.node)) {
64+
return false;
65+
}
66+
if (path.node.__seen) {
67+
return false;
68+
}
69+
if (path.scope.parent !== undefined) {
70+
return false;
71+
}
72+
if (types.isMemberExpression(path.parentPath.node)) {
73+
while (types.isMemberExpression(path.parentPath.node)) {
74+
if (path.node === path.parentPath.node.property) {
75+
return false;
76+
}
77+
path = path.parentPath;
78+
}
79+
}
80+
return true;
81+
}
82+
83+
function parsePropVariantExpression(expression) {
84+
let node = parse(expression);
85+
traverse(node, {
86+
enter(path) {
87+
if (isPropReference(path)) {
88+
let nextNode = expr`props.${path.node}`;
89+
nextNode.object.__seen = true;
90+
path.replaceWith(nextNode);
91+
}
92+
}
93+
});
94+
node = node.program.body[0].expression;
95+
return node;
96+
}
97+
4998
function findComponentNames(node: CSSNode): Array<string> {
5099
let componentNames = [];
51100
let selector = parseSelector(node.selector);
@@ -61,18 +110,40 @@ function findVariants(node: CSSNode): Array<VariantSpec> {
61110
let variantNames = [];
62111
let selector = parseSelector(node.selector);
63112
selector.eachPseudo(selector => {
113+
let expression = null;
114+
let variantName = selector.value.slice(1);
115+
116+
if (selector.value === PROP_VARIANT_NAME) {
117+
expression = node.selector.slice(
118+
selector.source.start.column + PROP_VARIANT_NAME.length,
119+
selector.source.end.column - 1
120+
);
121+
variantName = variantName + '__' + hash(expression).slice(0, 6);
122+
expression = parsePropVariantExpression(expression);
123+
}
124+
64125
let idx = selector.parent.nodes.indexOf(selector);
65126
let prev = selector.parent.nodes[idx - 1];
66127
if (prev && prev.type === 'tag' && COMPONENT_RE.exec(prev.value)) {
67128
variantNames.push({
68129
componentName: prev.value,
69-
variantName: selector.value.slice(1),
130+
variantName,
131+
expression,
70132
});
71133
}
72134
});
73135
return variantNames;
74136
}
75137

138+
function renderPropVariantCondition(selector) {
139+
let value = '';
140+
selector.eachInside(selector => {
141+
console.log(selector);
142+
value = value + (selector.value || '');
143+
});
144+
return value;
145+
}
146+
76147
function isPrimaryComponent(node: CSSNode): boolean {
77148
let selector = parseSelector(node.selector);
78149
return (
@@ -131,6 +202,13 @@ function localizeComponentRule(node) {
131202
let prev = parent.nodes[idx - 1];
132203
let componentName = prev.value;
133204
let variantName = selector.value.slice(1);
205+
if (selector.value === PROP_VARIANT_NAME) {
206+
let expression = node.selector.slice(
207+
selector.source.start.column + PROP_VARIANT_NAME.length,
208+
selector.source.end.column - 1
209+
);
210+
variantName = variantName + '__' + hash(expression).slice(0, 6);
211+
}
134212
let nextSelector = className({value: componentName + '__' + variantName});
135213
prev.removeSelf();
136214
selector.replaceWith(nextSelector);
@@ -164,12 +242,12 @@ function renderToJS(source: string, config: RenderConfig): string {
164242
}
165243
}
166244

167-
function registerComponentVariants(componentName, variantName) {
245+
function registerComponentVariants({componentName, variantName, expression}) {
168246
invariant(
169247
components[componentName],
170248
'Trying to configure base for an unknown component %s', componentName
171249
);
172-
components[componentName].variants[variantName] = true;
250+
components[componentName].variants[variantName] = {expression};
173251
}
174252

175253
function configureComponentBase(componentName, base) {
@@ -220,10 +298,7 @@ function renderToJS(source: string, config: RenderConfig): string {
220298
let variants = findVariants(node);
221299
for (let i = 0; i < variants.length; i++) {
222300
let variant = variants[i];
223-
registerComponentVariants(
224-
variant.componentName,
225-
variant.variantName
226-
);
301+
registerComponentVariants(variant);
227302
}
228303
});
229304

@@ -233,11 +308,20 @@ function renderToJS(source: string, config: RenderConfig): string {
233308
if (components.hasOwnProperty(componentName)) {
234309
let className = expr`styles.${identifier(componentName)}`;
235310
for (let variantName in component.variants) {
236-
className = expr`
237-
${className} + (variant.${identifier(variantName)}
238-
? ' ' + styles.${identifier(componentName + '__' + variantName)}
239-
: '')
240-
`;
311+
let variant = component.variants[variantName];
312+
if (variant.expression) {
313+
className = expr`
314+
${className} + (${variant.expression}
315+
? ' ' + styles.${identifier(componentName + '__' + variantName)}
316+
: '')
317+
`;
318+
} else {
319+
className = expr`
320+
${className} + (variant.${identifier(variantName)}
321+
? ' ' + styles.${identifier(componentName + '__' + variantName)}
322+
: '')
323+
`;
324+
}
241325
}
242326
statements.push(stmt`
243327
export function ${identifier(componentName)}({variant, ...props}) {

0 commit comments

Comments
 (0)