Skip to content

Commit

Permalink
Append hashed selector to class name instead of hashing value only (#127
Browse files Browse the repository at this point in the history
)

Resolves #126
  • Loading branch information
brandongregoryscott authored Nov 29, 2022
1 parent ef1cbea commit 9b9837d
Show file tree
Hide file tree
Showing 11 changed files with 191 additions and 122 deletions.
2 changes: 1 addition & 1 deletion .storybook/config.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { configure } from '@storybook/react'

function loadStories() {
require('../tools/story')
require('../tools/box.stories')
}

configure(loadStories, module)
6 changes: 5 additions & 1 deletion src/get-class-name.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,13 +37,17 @@ export default function getClassName(propertyInfo: PropertyInfo, value: string,
/* Always hash values that contain a calc() because the operators get
stripped which can result in class name collisions
*/
} else if (selector || complexValue || value.includes('calc(')) {
} else if (complexValue || value.includes('calc(')) {
valueKey = hash(value)
} else if (safeValue) {
valueKey = value
} else {
valueKey = getSafeValue(value)
}

if (selector) {
valueKey = `${valueKey}_${hash(selector)}`
}

return `${PREFIX}${className}_${valueKey}`
}
10 changes: 5 additions & 5 deletions test/enhance-props.ts
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,7 @@ test.serial('converts styles in selectors to class name', t => {
}
})

t.deepEqual(className, 'ub-bg-clr_nfznl2')
t.deepEqual(className, 'ub-bg-clr_blue_1k2el8q')
t.deepEqual(enhancedProps, {})
})

Expand All @@ -111,7 +111,7 @@ test.serial('injects selector styles', t => {
t.deepEqual(
styles.getAll(),
`
.ub-bg-clr_nfznl2:hover {
.ub-bg-clr_blue_1k2el8q:hover {
background-color: blue;
}`
)
Expand All @@ -130,7 +130,7 @@ test.serial('converts styles in nested selectors to class name', t => {
}
})

t.deepEqual(className, 'ub-bg-clr_nfznl2')
t.deepEqual(className, 'ub-bg-clr_blue_187q98e')
t.deepEqual(enhancedProps, {})
})

Expand All @@ -145,7 +145,7 @@ test.serial("selectors can be nested without 'selectors' key", t => {
}
})

t.deepEqual(className, 'ub-bg-clr_nfznl2')
t.deepEqual(className, 'ub-bg-clr_blue_187q98e')
t.deepEqual(enhancedProps, {})
})

Expand All @@ -165,7 +165,7 @@ test.serial('injects nested selector styles', t => {
t.deepEqual(
styles.getAll(),
`
.ub-bg-clr_nfznl2[data-active]:hover {
.ub-bg-clr_blue_187q98e[data-active]:hover {
background-color: blue;
}`
)
Expand Down
8 changes: 4 additions & 4 deletions test/get-class-name.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import test from 'ava'
import getClassName, { setClassNamePrefix, getClassNamePrefix } from '../src/get-class-name'
import getClassName, { setClassNamePrefix } from '../src/get-class-name'

test('supports inherit', t => {
t.is(getClassName({ className: 'w' }, 'inherit'), 'ub-w_inherit')
Expand Down Expand Up @@ -36,9 +36,9 @@ test('always hashes values that contain a calc()', t => {
t.is(result, 'ub-w_1vuvdht')
})

test('always hashes when a selector is provided', t => {
const result = getClassName({ className: 'bg-clr' }, 'blue', '&:hover')
t.is(result, 'ub-bg-clr_nfznl2')
test('always appends hash when a selector is provided', t => {
const result = getClassName({ className: 'bg-clr' }, 'blue', ':hover')
t.is(result, 'ub-bg-clr_blue_1k2el8q')
})

test('allows custom classname prefixes', t => {
Expand Down
25 changes: 19 additions & 6 deletions test/get-css.ts
Original file line number Diff line number Diff line change
Expand Up @@ -87,9 +87,9 @@ test('appends selector when present', t => {
const result = getCss(propInfo, 'blue', ':hover')

t.deepEqual(result, {
className: 'ub-bg-clr_nfznl2',
className: 'ub-bg-clr_blue_1k2el8q',
styles: `
.ub-bg-clr_nfznl2:hover {
.ub-bg-clr_blue_1k2el8q:hover {
background-color: blue;
}`,
rules: [{ property: 'background-color', value: 'blue' }]
Expand All @@ -106,15 +106,28 @@ test('expands comma-separated selectors for class name', t => {
const result = getCss(propInfo, 'blue', ':hover,[data-active=true]')

t.deepEqual(result, {
className: 'ub-bg-clr_nfznl2',
className: 'ub-bg-clr_blue_vwpa9u',
styles: `
.ub-bg-clr_nfznl2:hover, .ub-bg-clr_nfznl2[data-active=true] {
.ub-bg-clr_blue_vwpa9u:hover, .ub-bg-clr_blue_vwpa9u[data-active=true] {
background-color: blue;
}`,
rules: [{ property: 'background-color', value: 'blue' }]
})
})

test('props with same value but different selector should be unique', t => {
const propInfo = {
className: 'bg-clr',
cssName: 'background-color',
jsName: 'backgroundColor'
}

const blueHover = getCss(propInfo, 'blue', ':hover')
const blueDisabled = getCss(propInfo, 'blue', ':disabled')

t.notDeepEqual(blueHover!.className, blueDisabled!.className)
})

test('maintains whitespace when expanding comma-separated selectors', t => {
const propInfo = {
className: 'bg-clr',
Expand All @@ -124,11 +137,11 @@ test('maintains whitespace when expanding comma-separated selectors', t => {

const result = getCss(propInfo, 'blue', '.fancy-link:hover, .fancy-input:active')
t.deepEqual(result, {
className: 'ub-bg-clr_nfznl2',
className: 'ub-bg-clr_blue_ua1hgg',
// Intentionally expecting '.fancy-link' to be up against main class name since it does not start w/ a space
// while ' .fancy-input' should maintain space
styles: `
.ub-bg-clr_nfznl2.fancy-link:hover, .ub-bg-clr_nfznl2 .fancy-input:active {
.ub-bg-clr_blue_ua1hgg.fancy-link:hover, .ub-bg-clr_blue_ua1hgg .fancy-input:active {
background-color: blue;
}`,
rules: [{ property: 'background-color', value: 'blue' }]
Expand Down
4 changes: 2 additions & 2 deletions test/snapshots/box.tsx.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ Generated by [AVA](https://ava.li).
> DOM
<div
className="ub-a-nm_openAnimation_65p985 ub-a-dur_2-5s ub-a-itr-ct_infinite ub-a-tmng-fn_lvnx00 ub-a-dir_both ub-a-ply-ste_running ub-a-dly_0s ub-algn-cnt_center ub-algn-itms_center ub-algn-slf_center ub-bg_169mlyl ub-bg-blnd-md_multiply ub-bg-clp_padding-box ub-bg-clr_red ub-bg-img_181xl07 ub-bg-orgn_border-box ub-bg-pos_center ub-bg-rpt_no-repeat ub-bg-siz_cover ub-b-btm_1px-solid-black ub-b-lft_1px-solid-black ub-b-rgt_1px-solid-black ub-b-top_1px-solid-black ub-b-btm-clr_red ub-bblr_5px ub-bbrr_5px ub-b-btm-stl_dashed ub-b-btm-wdt_2px ub-b-lft-clr_red ub-b-rgt-clr_red ub-b-top-clr_red ub-b-lft-stl_dashed ub-b-lft-wdt_2px ub-btlr_5px ub-btrr_5px ub-b-rgt-stl_dashed ub-b-rgt-wdt_2px ub-b-top-stl_dashed ub-b-top-wdt_2px ub-btm_10px ub-bs_1yidkis ub-box-szg_border-box ub-clr_both ub-clearfix ub-color_blue ub-col-gap_3px ub-cnt_1qde24a ub-crsr_pointer ub-dspl_flex ub-fill_black ub-flx_1 ub-flx-basis_10px ub-flx-drct_column ub-flx-flow_column-wrap ub-flx-grow_1 ub-flx-srnk_1 ub-flx-wrap_wrap ub-flt_left ub-fnt_foz7o3 ub-fnt-fam_1bil31i ub-fnt-sze_14px ub-fnt-stl_italic ub-f-vari_small-caps ub-f-wght_bold ub-gap_3px ub-grd_1lsuugy ub-grd-ara_15fp4eo ub-grd-ato-col_1mp9azz ub-grd-ato-flw_row-dense ub-grd-ato-row_1mp9azz ub-grd-col_1i1wt59 ub-grd-col-end_span-3 ub-grd-col-gap_3px ub-grd-col-str_span-3 ub-grd-gap_3px ub-grd-row_1i1wt59 ub-grd-row-end_span-3 ub-grd-row-gap_3px ub-grd-row-str_span-3 ub-grd-tmp_1b7k023 ub-grd-tmp-ara_1s7jr1l ub-grd-tmp-col_2czdni ub-grd-tmp-row_2czdni ub-h_100px ub-just-cnt_center ub-just-items_center ub-just-self_center ub-lft_10px ub-ltr-spc_0-4em ub-ln-ht_1-2 ub-ls_1j8swju ub-ls-img_iu2jf4 ub-ls-pos_inside ub-ls-typ_lower-greek ub-mb_10px ub-ml_10px ub-mr_10px ub-mt_10px ub-max-h_100prcnt ub-max-w_100prcnt ub-min-h_100px ub-min-w_100px ub-opct_1 ub-order_1 ub-otln_iu2jf4 ub-ovflw-x_auto ub-ovflw-y_auto ub-pb_10px ub-pl_10px ub-pr_10px ub-pt_10px ub-plc-cnt_center-center ub-plc-items_center-center ub-plc-self_center-center ub-ptr-evts_auto ub-pst_relative ub-rsz_none ub-rgt_10px ub-row-gap_3px ub-bg-clr_nfznl2 ub-strk_black ub-strk-dshary_10 ub-strk-dshofst_10 ub-strk-lncp_round ub-strk-mtrlmt_10 ub-strk-w_10 ub-txt-algn_right ub-txt-deco_underline-dotted ub-txt-ovrf_ellipsis ub-txt-shdw_1kcg6ht ub-txt-trns_capitalize ub-top_10px ub-tfrm_yf7huz ub-tfrm-orgn_tkeb9y ub-tstn_7vzx02 ub-tstn-dly_ru3mkq ub-tstn-drn_i11nm5 ub-tstn-pty_ayd6f0 ub-tstn-tf_r9onko ub-usr-slct_none ub-vsblt_visible ub-wht-spc_nowrap ub-w_10ae43h ub-wrd-brk_normal ub-wrd-wrp_break-word ub-z-idx_1"
className="ub-a-nm_openAnimation_65p985 ub-a-dur_2-5s ub-a-itr-ct_infinite ub-a-tmng-fn_lvnx00 ub-a-dir_both ub-a-ply-ste_running ub-a-dly_0s ub-algn-cnt_center ub-algn-itms_center ub-algn-slf_center ub-bg_169mlyl ub-bg-blnd-md_multiply ub-bg-clp_padding-box ub-bg-clr_red ub-bg-img_181xl07 ub-bg-orgn_border-box ub-bg-pos_center ub-bg-rpt_no-repeat ub-bg-siz_cover ub-b-btm_1px-solid-black ub-b-lft_1px-solid-black ub-b-rgt_1px-solid-black ub-b-top_1px-solid-black ub-b-btm-clr_red ub-bblr_5px ub-bbrr_5px ub-b-btm-stl_dashed ub-b-btm-wdt_2px ub-b-lft-clr_red ub-b-rgt-clr_red ub-b-top-clr_red ub-b-lft-stl_dashed ub-b-lft-wdt_2px ub-btlr_5px ub-btrr_5px ub-b-rgt-stl_dashed ub-b-rgt-wdt_2px ub-b-top-stl_dashed ub-b-top-wdt_2px ub-btm_10px ub-bs_1yidkis ub-box-szg_border-box ub-clr_both ub-clearfix ub-color_blue ub-col-gap_3px ub-cnt_1qde24a ub-crsr_pointer ub-dspl_flex ub-fill_black ub-flx_1 ub-flx-basis_10px ub-flx-drct_column ub-flx-flow_column-wrap ub-flx-grow_1 ub-flx-srnk_1 ub-flx-wrap_wrap ub-flt_left ub-fnt_foz7o3 ub-fnt-fam_1bil31i ub-fnt-sze_14px ub-fnt-stl_italic ub-f-vari_small-caps ub-f-wght_bold ub-gap_3px ub-grd_1lsuugy ub-grd-ara_15fp4eo ub-grd-ato-col_1mp9azz ub-grd-ato-flw_row-dense ub-grd-ato-row_1mp9azz ub-grd-col_1i1wt59 ub-grd-col-end_span-3 ub-grd-col-gap_3px ub-grd-col-str_span-3 ub-grd-gap_3px ub-grd-row_1i1wt59 ub-grd-row-end_span-3 ub-grd-row-gap_3px ub-grd-row-str_span-3 ub-grd-tmp_1b7k023 ub-grd-tmp-ara_1s7jr1l ub-grd-tmp-col_2czdni ub-grd-tmp-row_2czdni ub-h_100px ub-just-cnt_center ub-just-items_center ub-just-self_center ub-lft_10px ub-ltr-spc_0-4em ub-ln-ht_1-2 ub-ls_1j8swju ub-ls-img_iu2jf4 ub-ls-pos_inside ub-ls-typ_lower-greek ub-mb_10px ub-ml_10px ub-mr_10px ub-mt_10px ub-max-h_100prcnt ub-max-w_100prcnt ub-min-h_100px ub-min-w_100px ub-opct_1 ub-order_1 ub-otln_iu2jf4 ub-ovflw-x_auto ub-ovflw-y_auto ub-pb_10px ub-pl_10px ub-pr_10px ub-pt_10px ub-plc-cnt_center-center ub-plc-items_center-center ub-plc-self_center-center ub-ptr-evts_auto ub-pst_relative ub-rsz_none ub-rgt_10px ub-row-gap_3px ub-bg-clr_blue_1k2el8q ub-strk_black ub-strk-dshary_10 ub-strk-dshofst_10 ub-strk-lncp_round ub-strk-mtrlmt_10 ub-strk-w_10 ub-txt-algn_right ub-txt-deco_underline-dotted ub-txt-ovrf_ellipsis ub-txt-shdw_1kcg6ht ub-txt-trns_capitalize ub-top_10px ub-tfrm_yf7huz ub-tfrm-orgn_tkeb9y ub-tstn_7vzx02 ub-tstn-dly_ru3mkq ub-tstn-drn_i11nm5 ub-tstn-pty_ayd6f0 ub-tstn-tf_r9onko ub-usr-slct_none ub-vsblt_visible ub-wht-spc_nowrap ub-w_10ae43h ub-wrd-brk_normal ub-wrd-wrp_break-word ub-z-idx_1"
contentEditable={true}
/>

Expand Down Expand Up @@ -395,7 +395,7 @@ Generated by [AVA](https://ava.li).
.ub-row-gap_3px {␊
row-gap: 3px;␊
}␊
.ub-bg-clr_nfznl2:hover {␊
.ub-bg-clr_blue_1k2el8q:hover {␊
background-color: blue;␊
}␊
.ub-strk_black {␊
Expand Down
Binary file modified test/snapshots/box.tsx.snap
Binary file not shown.
110 changes: 7 additions & 103 deletions tools/story.tsx → tools/box.stories.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
import React, { CSSProperties } from 'react'
import { default as Box, configureSafeHref, keyframes } from '../src'
import { default as Box, configureSafeHref, BoxProps } from '../src'
import { storiesOf } from '@storybook/react'
import allPropertiesComponent from './all-properties-component'
import { BoxProps } from '../src/types/box-types'
import SelectorUniqueness from './fixtures/selector-uniquness-story'
import KeyframesStory from './fixtures/keyframes-story'
import SelectorsStory from './fixtures/selectors-story'

const RedBox: React.FunctionComponent<BoxProps<'div'>> = redBoxProps => (
<Box background="red" width="100px" height="100px" margin="20px" {...redBoxProps} />
Expand Down Expand Up @@ -238,108 +240,10 @@ storiesOf('Box', module)
<RedBox marginLeft="5px" />
</Box>
))
.add('selectors', () => {
return (
<Box>
<Box>
Border style on hover
<Box
is="input"
selectors={{
'&:hover': { borderColor: 'red' }
}}
/>
</Box>
<Box>
No border style on hover - :not(:disabled) selector
<Box
is="input"
disabled={true}
selectors={{
'&:hover:not(:disabled)': { borderColor: 'blue' }
}}
/>
</Box>
<Box selectors={{ '& .child:hover': { backgroundColor: 'red' } }}>
Red background on child hover
<Box className="child" backgroundColor="blue" width={200} height={100} />
<Box className="child" backgroundColor="yellow" width={200} height={100} />
<Box className="child" backgroundColor="green" width={200} height={100} />
</Box>
<Box selectors={{ '& .foo:hover, .bar:hover': { backgroundColor: 'green' } }}>
Green background on child hover (comma-separated class name selectors)
<Box className="foo" backgroundColor="blue" width={200} height={100} />
<Box className="bar" backgroundColor="yellow" width={200} height={100} />
</Box>
Pink background on :focus or :hover
<Box selectors={{ '&:focus,:hover': { backgroundColor: 'pink' } }} width={200} height={100} />
Nested selector - blue background when <Box is="code">data-active=true</Box>, red background on hover
<Box
data-active={true}
height={100}
width={200}
selectors={{
'[data-active=true]': {
backgroundColor: 'blue',
'&:hover': {
backgroundColor: 'red'
}
}
}}
/>
</Box>
)
})
.add('selectors', () => <SelectorsStory />)
.add('selector uniqueness', () => <SelectorUniqueness />)
.add('style prop', () => {
const style: CSSProperties = { backgroundColor: 'red', width: 200 }
return <Box style={style}>{JSON.stringify(style, undefined, 4)}</Box>
})
.add('keyframes', () => {
const translateTo0 = {
transform: 'translate3d(0,0,0)'
}
const translateNeg30 = {
transform: 'translate3d(0, -30px, 0)'
}

const translateNeg15 = {
transform: 'translate3d(0, -15px, 0)'
}

const translateNeg4 = {
transform: 'translate3d(0,-4px,0)'
}

// Based on https://emotion.sh/docs/keyframes
const bounce = keyframes('bounce', {
from: translateTo0,
20: translateTo0,
40: translateNeg30,
43: translateNeg30,
53: translateTo0,
70: translateNeg15,
80: translateTo0,
90: translateNeg4,
to: translateTo0
})

return (
<Box>
Single prop
<Box animation={`${bounce} 1s ease 0s infinite normal none running`}>some bouncing text!</Box>
Separate props
<Box
animationName={bounce}
animationDuration="1s"
animationTimingFunction="ease"
animationDelay="0s"
animationIterationCount="infinite"
animationDirection="normal"
animationFillMode="none"
animationPlayState="running"
>
some bouncing text!
</Box>
</Box>
)
})
.add('keyframes', () => <KeyframesStory />)
54 changes: 54 additions & 0 deletions tools/fixtures/keyframes-story.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
import React from 'react'
import Box, { keyframes } from '../../src'

const KeyframesStory: React.FC = () => {
const translateTo0 = {
transform: 'translate3d(0,0,0)'
}
const translateNeg30 = {
transform: 'translate3d(0, -30px, 0)'
}

const translateNeg15 = {
transform: 'translate3d(0, -15px, 0)'
}

const translateNeg4 = {
transform: 'translate3d(0,-4px,0)'
}

// Based on https://emotion.sh/docs/keyframes
const bounce = keyframes('bounce', {
from: translateTo0,
20: translateTo0,
40: translateNeg30,
43: translateNeg30,
53: translateTo0,
70: translateNeg15,
80: translateTo0,
90: translateNeg4,
to: translateTo0
})

return (
<Box>
Single prop
<Box animation={`${bounce} 1s ease 0s infinite normal none running`}>some bouncing text!</Box>
Separate props
<Box
animationName={bounce}
animationDuration="1s"
animationTimingFunction="ease"
animationDelay="0s"
animationIterationCount="infinite"
animationDirection="normal"
animationFillMode="none"
animationPlayState="running"
>
some bouncing text!
</Box>
</Box>
)
}

export default KeyframesStory
37 changes: 37 additions & 0 deletions tools/fixtures/selector-uniquness-story.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import React, { useState } from 'react'
import Box from '../../src'

const SelectorUniqueness: React.FC = () => {
const [isInputDisabled, setIsInputDisabled] = useState(false)

return (
<Box display="flex" flexDirection="column" width={200}>
Border style on hover
<Box
is="input"
selectors={{
'&:hover': { borderColor: 'red' }
}}
/>
Border style only when disabled
<Box>
Disable input
<Box
is="input"
type="checkbox"
onChange={() => setIsInputDisabled((disabled: boolean) => !disabled)}
checked={isInputDisabled}
/>
</Box>
<Box
is="input"
disabled={isInputDisabled}
selectors={{
'&:disabled': { borderColor: 'red' }
}}
/>
</Box>
)
}

export default SelectorUniqueness
Loading

0 comments on commit 9b9837d

Please sign in to comment.