From 0a18c22a50756db635ec9ea9a32871a39f4f9bf2 Mon Sep 17 00:00:00 2001 From: Jevgeni Geimanen Date: Sat, 28 Nov 2015 12:04:42 +0100 Subject: [PATCH] Initial, stable commit --- .eslintrc | 26 ++++++ .gitignore | 2 + dist/react-bem-my-style.js | 164 +++++++++++++++++++++++++++++++++++++ gulpfile.coffee | 42 ++++++++++ package.json | 42 ++++++++++ src/react-bem-my-style.js | 122 +++++++++++++++++++++++++++ 6 files changed, 398 insertions(+) create mode 100644 .eslintrc create mode 100644 .gitignore create mode 100644 dist/react-bem-my-style.js create mode 100644 gulpfile.coffee create mode 100644 package.json create mode 100644 src/react-bem-my-style.js diff --git a/.eslintrc b/.eslintrc new file mode 100644 index 0000000..b519b5a --- /dev/null +++ b/.eslintrc @@ -0,0 +1,26 @@ +{ + "ecmaFeatures": { + "jsx": true, + "modules": true, + "experimentalObjectRestSpread": true + }, + "env": { + "browser": true, + "node": true, + "es6": true + }, + "parser": "babel-eslint", + "rules": { + "indent": [2, 4], + "quotes": [2, "double"], + "linebreak-style": [2, "unix"], + "semi": [2, "always"], + "react/jsx-uses-react": 2, + "react/jsx-uses-vars": 2, + "react/react-in-jsx-scope": 2 + }, + "plugins": [ + "react" + ], + "extends": "eslint:recommended" +} diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..34977ee --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +node_modules +.idea \ No newline at end of file diff --git a/dist/react-bem-my-style.js b/dist/react-bem-my-style.js new file mode 100644 index 0000000..2315110 --- /dev/null +++ b/dist/react-bem-my-style.js @@ -0,0 +1,164 @@ +define(["exports", "module"], function (exports, module) { + /*global console */ + "use strict"; + + function _toConsumableArray(arr) { if (Array.isArray(arr)) { for (var i = 0, arr2 = Array(arr.length); i < arr.length; i++) arr2[i] = arr[i]; return arr2; } else { return Array.from(arr); } } + + function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; } + + var CHILD_TYPE_ELEMENT = 1; + var CHILD_TYPE_MODIFIER = 2; + var MODIFIER_SEPARATOR = "--"; + var ELEMENT_SEPARATOR = "__"; + var NAME_FIELD_NAME = "__name"; + + var getSeparator = function getSeparator(value) { + return isModifier(value) ? MODIFIER_SEPARATOR : ELEMENT_SEPARATOR; + }; + + var logError = function logError(errorMsg) { + throw new Error(errorMsg); + }; + + /** + * Converts string from CamelCase to "dashed-string" + * @example + * camelCaseToDashed("thisIsCamelCasedString"); // "this-is-camel-cased-string" + * + * @param {string} txt - string that needs to be Dashed + * @return {string} + */ + var camelCaseToDashed = function camelCaseToDashed(txt) { + return txt.replace(/([a-z])([A-Z])/g, "$1-$2").toLowerCase(); + }; + + /** + * Returns object, ready for JSX destruction + * + * @param {string|string[]} classes - single className or an array of classNames + * @return {{className:string}} + */ + var wrapWithClassName = function wrapWithClassName(classes) { + return { className: typeof classes === "string" ? classes : classes.join(" ") }; + }; + + /** + * + * @param {CHILD_TYPE_MODIFIER} value + * @return {boolean} + */ + var isModifier = function isModifier(value) { + return value === CHILD_TYPE_MODIFIER; + }; + + /** + * + * @param {CHILD_TYPE_ELEMENT} value + * @return {boolean} + */ + var isElement = function isElement(value) { + return value === CHILD_TYPE_ELEMENT; + }; + + /** + * Returns Element BEM Generator function + * + * @param {string} name - name of the element + * @param {string[]} allModifiers - array of names of element's modifiers + * @return {Function} + */ + var makeElementGenerator = function makeElementGenerator(name, allModifiers) { + return function () { + for (var _len = arguments.length, rest = Array(_len), _key = 0; _key < _len; _key++) { + rest[_key] = arguments[_key]; + } + + if (rest.length > 0) { + var _ret = (function () { + var modifiers = rest[0]; + + if (typeof modifiers !== "object") { + logError("Object expected " + modifiers + " received"); + } + + var keys = Object.keys(modifiers); + var missingModifiers = allModifiers.filter(function (modifier) { + return keys.indexOf(modifier) === -1; + }); + + if (missingModifiers.length > 0) { + logError(name + " is missing modifiers: " + missingModifiers.join(",")); + } + + return { + v: wrapWithClassName([name].concat(_toConsumableArray(keys.filter(function (key) { + return modifiers[key]; + }).map(function (key) { + return modifiers[key]; + })))) + }; + })(); + + if (typeof _ret === "object") return _ret.v; + } else if (allModifiers.length) { + logError(name + " is missing modifiers: " + allModifiers.join(",")); + } + return wrapWithClassName(camelCaseToDashed(name)); + }; + }; + + /** + * Returns BEM Generator for Element and it's Child elements and modifiers + * + * @param {string} name - name of element + * @param {{name:object}} block - child elements and modifiers of the element + * @param {string[]} allModifiers - array of names of element's modifiers + * @return {Function} + */ + var makeElement = function makeElement(name, block, allModifiers) { + return Object.assign(makeElementGenerator(name, allModifiers), Object.keys(block).reduce(function (mem, childName) { + if (childName !== NAME_FIELD_NAME) { + (function () { + var fullName = "" + name + getSeparator(block[childName]) + camelCaseToDashed(childName); + + if (isModifier(block[childName])) { + mem[childName] = function (s) { + return _defineProperty({}, childName, s === true && fullName); + }; + } else if (isElement(block[childName])) { + mem[childName] = function () { + return wrapWithClassName(fullName); + }; + } else if (typeof block[childName] === "object") { + mem[childName] = makeBem(fullName, block[childName]); + } else { + logError("Unexpected value for " + name + "(" + childName + ":" + block[childName] + ")"); + } + })(); + } + return mem; + }, {})); + }; + + /** + * Returns BEM Generator for Elementand it's Child elements and modifiers + * + * @param {string} name - name of the element + * @param {{name:object}} block - child elements and modifiers of the element + */ + var makeBem = function makeBem(name, block) { + return makeElement(name, block, Object.keys(block).filter(function (childName) { + return block[childName] === CHILD_TYPE_MODIFIER; + })); + }; + + /** + * Proxies return to makeBem + * + * @param {{name:object}} block - child elements and modifiers of the element + */ + + module.exports = function (block) { + return makeBem(camelCaseToDashed(block[NAME_FIELD_NAME]), block); + }; +}); \ No newline at end of file diff --git a/gulpfile.coffee b/gulpfile.coffee new file mode 100644 index 0000000..3bee44a --- /dev/null +++ b/gulpfile.coffee @@ -0,0 +1,42 @@ +gulp = require "gulp" +gutil = require "gulp-util" +cache = require "gulp-cached" +changed = require "gulp-changed" +babel = require "gulp-babel" +plumber = require "gulp-plumber" +watch = require "gulp-watch" +notify = require "gulp-notify" +runSequence = require "run-sequence" + + +src = "./src/*.js" +dest = "./dist" + +gulp.task "build", ["babel"] +gulp.task "default", ["watch"] +gulp.task "babel", -> + gulp.src src + .pipe plumber( + errorHandler: notify.onError( + "Babel build error: <%= error.name %> <%= error.message %>" + ) + ) + .pipe cache "babel" + .pipe changed dest, extension: ".js" + + .pipe babel + modules: "amd" + .on "error", (error) -> + @hadError = true + gutil.log( + gutil.colors.red( + "#{error.name}: #{error.message} (#{error.fileName})" + ) + ) + .pipe gulp.dest dest + +gulp.task "watch", ["build"], -> + watch src, -> + runSequence "babel" + gutil.log "Watcher started" + diff --git a/package.json b/package.json new file mode 100644 index 0000000..1158931 --- /dev/null +++ b/package.json @@ -0,0 +1,42 @@ +{ + "name": "react-bem-my-style", + "version": "0.0.1", + "description": "Turns BEM definition into JSX-eatable object", + "main": "dist/index.js", + "homepage": "https://github.com/evolution-gaming/react-bem-my-style", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + }, + "bugs": { + "url": "https://github.com/evolution-gaming/react-bem-my-style/issues" + }, + "repository": { + "type": "git", + "url": "git+https://github.com/evolution-gaming/react-bem-my-style.git" + }, + "keywords": [ + "react", + "css", + "bem" + ], + "author": "Jevgeni Geimanen", + "maintainers": [ + { + "name": "jevgenig", + "email": "jgeimanen@evolutiongaming.com" + } + ], + "license": "BSD-3-Clause", + "devDependencies": { + "coffee-script": "^1.10.0", + "eslint": "^1.10.2", + "gulp": "^3.9.0", + "gulp-babel": "5.1.0", + "gulp-cached": "^1.1.0", + "gulp-notify": "^2.2.0", + "gulp-plumber": "^1.0.1", + "gulp-util": "^3.0.7", + "gulp-watch": "^4.3.5", + "run-sequence": "^1.1.5" + } +} diff --git a/src/react-bem-my-style.js b/src/react-bem-my-style.js new file mode 100644 index 0000000..a251134 --- /dev/null +++ b/src/react-bem-my-style.js @@ -0,0 +1,122 @@ +/*global console */ +const CHILD_TYPE_ELEMENT = 1; +const CHILD_TYPE_MODIFIER = 2; +const MODIFIER_SEPARATOR = "--"; +const ELEMENT_SEPARATOR = "__"; +const NAME_FIELD_NAME = "__name"; + +const getSeparator = (value)=>isModifier(value) ? MODIFIER_SEPARATOR : ELEMENT_SEPARATOR; + +const logError = (errorMsg) => { + throw new Error(errorMsg); +}; + +/** + * Converts string from CamelCase to "dashed-string" + * @example + * camelCaseToDashed("thisIsCamelCasedString"); // "this-is-camel-cased-string" + * + * @param {string} txt - string that needs to be Dashed + * @return {string} + */ +const camelCaseToDashed = (txt) => + txt.replace(/([a-z])([A-Z])/g, "$1-$2").toLowerCase(); + +/** + * Returns object, ready for JSX destruction + * + * @param {string|string[]} classes - single className or an array of classNames + * @return {{className:string}} + */ +const wrapWithClassName = (classes) => + ({className: typeof classes === "string" ? classes : classes.join(" ")}); + +/** + * + * @param {CHILD_TYPE_MODIFIER} value + * @return {boolean} + */ +const isModifier = (value) => value === CHILD_TYPE_MODIFIER; + +/** + * + * @param {CHILD_TYPE_ELEMENT} value + * @return {boolean} + */ +const isElement = (value) => value === CHILD_TYPE_ELEMENT; + +/** + * Returns Element BEM Generator function + * + * @param {string} name - name of the element + * @param {string[]} allModifiers - array of names of element's modifiers + * @return {Function} + */ +const makeElementGenerator = (name, allModifiers) => + (...rest) => { + if (rest.length > 0) { + const modifiers = rest[0]; + + if (typeof modifiers !== "object") { + logError(`Object expected ${modifiers} received`); + } + + const keys = Object.keys(modifiers); + const missingModifiers = allModifiers.filter(modifier => keys.indexOf(modifier) === -1); + + if (missingModifiers.length > 0) { + logError(`${name} is missing modifiers: ${missingModifiers.join(",")}`); + } + + return wrapWithClassName([name, ...keys.filter(key => modifiers[key]).map(key => modifiers[key])]); + } else if (allModifiers.length) { + logError(`${name} is missing modifiers: ${allModifiers.join(",")}`); + } + return wrapWithClassName(camelCaseToDashed(name)); + }; + +/** + * Returns BEM Generator for Element and it's Child elements and modifiers + * + * @param {string} name - name of element + * @param {{name:object}} block - child elements and modifiers of the element + * @param {string[]} allModifiers - array of names of element's modifiers + * @return {Function} + */ +const makeElement = (name, block, allModifiers) => + Object.assign( + makeElementGenerator(name, allModifiers), + Object.keys(block).reduce((mem, childName) => { + if ((childName) !== NAME_FIELD_NAME) { + const fullName = `${name}${getSeparator(block[childName])}${camelCaseToDashed(childName)}`; + + if (isModifier(block[childName])) { + mem[childName] = (s) => ({[childName]: s === true && fullName}); + } else if (isElement(block[childName])) { + mem[childName] = () => wrapWithClassName(fullName); + } else if (typeof block[childName] === "object") { + mem[childName] = makeBem(fullName, block[childName]); + } else { + logError(`Unexpected value for ${name}(${childName}:${block[childName]})`); + } + } + return mem; + }, {}) + ); + +/** + * Returns BEM Generator for Elementand it's Child elements and modifiers + * + * @param {string} name - name of the element + * @param {{name:object}} block - child elements and modifiers of the element + */ +const makeBem = (name, block) => + makeElement(name, block, Object.keys(block).filter(childName => block[childName] === CHILD_TYPE_MODIFIER)); + +/** + * Proxies return to makeBem + * + * @param {{name:object}} block - child elements and modifiers of the element + */ +export default (block) => + makeBem(camelCaseToDashed(block[NAME_FIELD_NAME]), block);