diff --git a/src/index.js b/src/index.js
index bc88d49..7a7bb45 100644
--- a/src/index.js
+++ b/src/index.js
@@ -1,46 +1,50 @@
import React from 'react'
-let JOIN_MODIFIERS = '-'
-let JOIN_WORDS = '-'
-let TRANSFORM_CASE = true
+const DEFAULT_CONFIG = {
+ transformCase: true,
+ join: {
+ block: '-',
+ modifier: '-',
+ value: '-',
+ words: '-'
+ }
+}
+
+let config = DEFAULT_CONFIG
export function configure(opts) {
- JOIN_MODIFIERS = (opts.join && opts.join.modifiers) || JOIN_MODIFIERS
- JOIN_WORDS = (opts.join && opts.join.words) || JOIN_WORDS
- TRANSFORM_CASE = opts.hasOwnProperty('transformCase')
- ? Boolean(opts['transformCase'])
- : TRANSFORM_CASE
+ config = {
+ ...DEFAULT_CONFIG,
+ ...opts,
+ join: { ...DEFAULT_CONFIG.join, ...opts.join }
+ }
}
-function transformName(name) {
+function toStyleName(modifier, value) {
+ const name =
+ value === true ? modifier : `${modifier}${config.join.value}${value}`
// Might need to customize the separator
// This is aZ | aXYZ
let style = name
.split(/(?<=[a-z])(?=[A-Z])|(?<=[A-Z])(?=[A-Z][a-z])/)
- .join(JOIN_WORDS)
+ .join(config.join.words)
- if (TRANSFORM_CASE) {
- style = style[0] + style.substring(1).toLowerCase()
+ if (config.transformCase) {
+ style = style.toLowerCase()
}
return style
}
-function toStyleName(name, modifiers) {
- return transformName(
- modifiers ? `${name}${JOIN_MODIFIERS}${modifiers}` : name
- )
-}
-
function toClassNames(props) {
return Object.keys(props)
- .filter(name => !!props[name])
+ .filter(name => props[name] !== false)
.map(name => {
if (Array.isArray(props[name])) {
return props[name].map(inner => toStyleName(name, inner)).join(' ')
}
- return toStyleName(name, props[name] === true ? undefined : props[name])
+ return toStyleName(name, props[name])
})
}
@@ -78,14 +82,46 @@ export const Box = ({
A wrapper that injects its children with style and className
using shallow merging and classNameTransform
*/
-export const Comp = ({ style, className, children, ...propClasses }) =>
+export const Comp = ({ as, style, className, children, ...propClasses }) =>
React.Children.map(
children,
child =>
child
? React.cloneElement(child, {
style: { ...style, ...child.props.style },
- className: cx(propClasses, className, child.props.className)
+ className: cx(
+ propClasses,
+ className,
+ child.props.className,
+ as && as.__classier
+ )
})
: null
)
+
+export function mapToNamespace(name, props) {
+ return Object.keys(props).reduce(
+ (res, key) => {
+ res[`${name}${config.join.modifier}${key}`] = props[key]
+ return res
+ },
+ { [name]: true }
+ )
+}
+
+export const createBlock = name => {
+ const render = props =>
+
+ const proxy = new Proxy(render, {
+ get(obj, nested) {
+ return nested in obj
+ ? obj[nested]
+ : (obj[nested] = createBlock(`${name}${config.join.block}${nested}`))
+ }
+ })
+
+ // Store this so we can use it in Comp
+ proxy.__classier = toStyleName(name, true)
+
+ return proxy
+}
diff --git a/test/blocks.test.js b/test/blocks.test.js
new file mode 100644
index 0000000..de9c106
--- /dev/null
+++ b/test/blocks.test.js
@@ -0,0 +1,36 @@
+const { mapToNamespace, createBlock } = require('../src')
+
+test('mapToNamespace', () => {
+ const res = mapToNamespace('wakka', {
+ chicken: 'dinner',
+ sum: 41
+ })
+
+ expect(res).toEqual({
+ wakka: true,
+ 'wakka-chicken': 'dinner',
+ 'wakka-sum': 41
+ })
+})
+
+describe('createBlock', () => {
+ test('should have __classier', () => {
+ const ns = createBlock('NSWeAreTesting')
+ expect(ns).toHaveProperty('__classier', 'ns-we-are-testing')
+ })
+
+ test('should proxy to factory', () => {
+ const ns = createBlock('NS')
+ expect(ns.TestEl).toHaveProperty('__classier', 'ns-test-el')
+ })
+
+ xtest('should render root', () => {
+ const NS = createBlock('NS')
+ expect().toMatchSnapshot()
+ })
+
+ xtest('should render proxy', () => {
+ const NS = createBlock('NS')
+ expect().toMatchSnapshot()
+ })
+})
diff --git a/test/classier.test.js b/test/classier.test.js
deleted file mode 100644
index 2b98128..0000000
--- a/test/classier.test.js
+++ /dev/null
@@ -1,105 +0,0 @@
-const { configure, cx, Box, Comp } = require('../src')
-
-describe('cx:', () => {
- test('should transform propClasses with booleans', () => {
- const res = cx({
- chicken: true,
- dinner: true
- })
-
- expect(res).toEqual('chicken dinner')
- })
-
- test('should transform propClasses with normal values', () => {
- const res = cx({
- chicken: 'dinner',
- sum: 41
- })
-
- expect(res).toEqual('chicken-dinner sum-41')
- })
-
- test('should transform propClasses with arrays', () => {
- const res = cx({
- chicken: ['tasty', 'dinner']
- })
-
- expect(res).toEqual('chicken-tasty chicken-dinner')
- })
-
- test('should transform propClasses with CamelCase names', () => {
- const res = cx({
- CamelHumps: 'LovelyCamelHumps'
- })
-
- expect(res).toEqual('Camel-humps-lovely-camel-humps')
- })
-})
-
-describe('configure:', () => {
- let configure, cx
- beforeEach(() => {
- jest.resetModules()
- const lib = require('../src')
- cx = lib.cx
- configure = lib.configure
- })
-
- test('should allow changing modifier symbol', () => {
- configure({
- join: {
- modifiers: '__'
- }
- })
- const res = cx({
- George: 'Foreman'
- })
-
- expect(res).toEqual('George__foreman')
- })
-
- test('should allow changing transformCase', () => {
- configure({
- transformCase: false
- })
- const res = cx({
- George: 'Foreman'
- })
-
- expect(res).toEqual('George-Foreman')
- })
-})
-
-describe('Box:', () => {
- xtest('should render with class name', () => {
- // expect(render()).toMatch(snapshot)
- })
-
- xtest('should render as span with class name', () => {
- // expect(render()).toMatch(snapshot)
- })
-
- xtest('should render with prop values in class names', () => {
- // expect(render()).toMatch(snapshot)
- })
-
- xtest('should ignore onClick', () => {
- // const fn = mock(() => ())
- // expect(render().click('click-me')).toMatch(snapshot)
- // expect(fn).toHaveBeenCalled().once()
- })
-})
-
-describe('Comp:', () => {
- xtest('should render with class name', () => {
- // expect(render(} />)).toMatch(snapshot)
- })
-
- xtest('should render with parametric class names', () => {
- // expect(render(} />)).toMatch(snapshot)
- })
-
- xtest('should inject style', () => {
- // expect(render(} />)).toMatch(snapshot)
- })
-})
diff --git a/test/react.test.js b/test/react.test.js
new file mode 100644
index 0000000..afec138
--- /dev/null
+++ b/test/react.test.js
@@ -0,0 +1,35 @@
+const { Box, Comp } = require('../src')
+
+describe('Box:', () => {
+ xtest('should render with class name', () => {
+ // expect(render()).toMatch(snapshot)
+ })
+
+ xtest('should render as span with class name', () => {
+ // expect(render()).toMatch(snapshot)
+ })
+
+ xtest('should render with prop values in class names', () => {
+ // expect(render()).toMatch(snapshot)
+ })
+
+ xtest('should ignore onClick', () => {
+ // const fn = mock(() => ())
+ // expect(render().click('click-me')).toMatch(snapshot)
+ // expect(fn).toHaveBeenCalled().once()
+ })
+})
+
+describe('Comp:', () => {
+ xtest('should render with class name', () => {
+ // expect(render(} />)).toMatch(snapshot)
+ })
+
+ xtest('should render with parametric class names', () => {
+ // expect(render(} />)).toMatch(snapshot)
+ })
+
+ xtest('should inject style', () => {
+ // expect(render(} />)).toMatch(snapshot)
+ })
+})
diff --git a/test/translation.test.js b/test/translation.test.js
new file mode 100644
index 0000000..ffb206d
--- /dev/null
+++ b/test/translation.test.js
@@ -0,0 +1,99 @@
+const { cx } = require('../src')
+
+describe('cx:', () => {
+ test('should transform propClasses with booleans', () => {
+ const res = cx({
+ chicken: true,
+ dinner: true
+ })
+
+ expect(res).toEqual('chicken dinner')
+ })
+
+ test('should transform propClasses with normal values', () => {
+ const res = cx({
+ chicken: 'dinner',
+ sum: 41
+ })
+
+ expect(res).toEqual('chicken-dinner sum-41')
+ })
+
+ test('should transform propClasses with arrays', () => {
+ const res = cx({
+ chicken: ['tasty', 'dinner']
+ })
+
+ expect(res).toEqual('chicken-tasty chicken-dinner')
+ })
+
+ test('should transform propClasses with CamelCase names', () => {
+ const res = cx({
+ CamelHumps: 'LovelyCamelHumps'
+ })
+
+ expect(res).toEqual('camel-humps-lovely-camel-humps')
+ })
+})
+
+describe('configure:', () => {
+ let configure, cx
+ beforeEach(() => {
+ jest.resetModules()
+ const lib = require('../src')
+ cx = lib.cx
+ configure = lib.configure
+ })
+
+ test('should allow changing value joiner', () => {
+ configure({
+ join: {
+ value: '__'
+ }
+ })
+ const res = cx({
+ George: 'Foreman'
+ })
+
+ expect(res).toEqual('george__foreman')
+ })
+
+ test('should allow changing modifier joiner', () => {
+ const { mapToNamespace } = require('../src')
+ configure({
+ join: {
+ modifier: '__'
+ }
+ })
+ const res = mapToNamespace('Mr', {
+ George: 'Foreman'
+ })
+
+ expect(res).toHaveProperty('Mr__George')
+ })
+
+ test('should allow changing block joiner', () => {
+ const { createBlock } = require('../src')
+ configure({
+ join: {
+ block: '__'
+ }
+ })
+ const MFG = createBlock('BlackAndDecker')
+
+ expect(MFG.GeorgeForeman.__classier).toEqual(
+ 'black-and-decker__george-foreman'
+ )
+ })
+
+ test('should allow changing transformCase', () => {
+ configure({
+ transformCase: false
+ })
+ const res = cx({
+ George: 'Foreman'
+ })
+
+ expect(res).toEqual('George-Foreman')
+ })
+})