Skip to content

Instantly share code, notes, and snippets.

@ASH-Bryan
Last active August 22, 2018 23:25
Show Gist options
  • Save ASH-Bryan/19c7ccf469446f054fc7ab7ef5a4685a to your computer and use it in GitHub Desktop.
Save ASH-Bryan/19c7ccf469446f054fc7ab7ef5a4685a to your computer and use it in GitHub Desktop.
css-bem
import Ember from 'ember';
export default Ember.Component.extend({
tagName: 'h1',
classNames: ['mediaCard__title']
});
import Ember from 'ember';
export default Ember.Component.extend({
classNames: ['mediaCard__actionButton']
});
import Ember from 'ember';
export default Ember.Component.extend({
});
import Ember from 'ember'
import { computed } from '@ember/object'
import { htmlSafe } from '@ember/string'
export default Ember.Component.extend({
classNames: ['mediaCard__actionButton'],
largeFab: false,
baseSize: computed('largeFab', function() {
return this.largeFab ? 65 : 50
})
});
import Ember from 'ember'
import { computed } from '@ember/object'
import { htmlSafe } from '@ember/string'
export default Ember.Component.extend({
tagName: 'section',
classNames: ['mediaCard'],
classNameBindings: ['dark:mediaCard--dark'],
showDescription: false,
largeFab: false,
image: null,
headerBackground: computed('image', function() {
return this.image
? htmlSafe(`background-image: url("${this.image}");`)
: null
}),
headerClass: computed('image', function() {
return this.image ? `h-48` : ''
}),
fabStyle: computed('largeFab', function() {
return this.largeFab
? htmlSafe('top: -3rem; right: 1rem;')
: htmlSafe('top: -2.5rem; right: 1rem;')
}),
actions: {
toggleDescription() {
this.toggleProperty('showDescription')
}
}
});
import Ember from 'ember';
export default Ember.Component.extend({
classNames: ['mediaCard__actionButton']
})
import Ember from 'ember';
export default Ember.Component.extend({
tagName: 'button',
classNames: ['mediaCard__button']
});
import Ember from 'ember';
export default Ember.Component.extend({
tagName: ''
}).reopenClass({
positionalParams: ['text']
});
import Ember from 'ember';
export default Ember.Controller.extend({
appName: 'CSS Playground'
});
import Ember from 'ember';
export default Ember.Controller.extend({
hasturDescription: `I found myself faced by names and terms that I had heard elsewhere in the most hideous of connections—Yuggoth, Great Cthulhu, Tsathoggua, Yog-Sothoth, R'lyeh, Nyarlathotep, Azathoth, Hastur, Yian, Leng, the Lake of Hali, Bethmoora, the Yellow Sign, L'mur-Kathulos, Bran and the Magnum Innominandum—and was drawn back through nameless aeons and inconceivable dimensions to worlds of elder, outer entity at which the crazed author of the Necronomicon had only guessed in the vaguest way.... There is a whole secret cult of evil men (a man of your mystical erudition will understand me when I link them with Hastur and the Yellow Sign) devoted to the purpose of tracking them down and injuring them on behalf of the monstrous powers from other dimensions.`,
yogLinks: [
{
name: 'Feed Me',
url: 'https://en.wikipedia.org/wiki/Yog-Sothoth'
},
{
name: 'Ask a Question',
url: 'https://en.wikipedia.org/wiki/Yog-Sothoth'
}
]
});
import EmberRouter from '@ember/routing/router';
import config from './config/environment';
const Router = EmberRouter.extend({
location: 'none',
rootURL: config.rootURL
});
Router.map(function() {
this.route('faq')
});
export default Router;
/* Plumbing classes: These are not part of the fun */
body {
padding: 0.25rem;
margin: 0.25rem;
}
.navButton {
border: 1px solid black;
padding: .3rem;
text-decoration: none;
color: blue;
}
.navButton.active {
color: white;
background-color: blue;
}
/* BEM classes */
.mediaCard {
box-shadow: 0 2px 4px 0 rgba(0,0,0,0.10);
border-radius: .25rem;
}
.mediaCard--dark {
background-color: #606f7b;
color: white;
}
.mediaCard__header {
display: flex;
padding: 0.75rem;
align-items: flex-end;
}
.mediaCard__header--image {
height: 12rem;
color: white;
}
.mediaCard__body {
position: relative;
padding: 0.75rem;
}
.mediaCard__body--title {
padding-top: 1rem;
}
.mediaCard__links {
padding: 0.75rem;
border-top: 1px solid #dae1e7;
margin-top: 0.5rem;
padding-top: 0.5rem;
}
.mediaCard__link {
color: #f2d024;
text-decoration: none;
margin-right: 1.5rem;
}
.mediaCard__title {
display: flex;
justify-content: space-between;
margin: 0rem;
}
.mediaCard__button {
color: white;
background-color: #4dc0b5;
border-radius: .25rem;
display: flex;
}
.mediaCard__buttonContainer {
padding: 0.5rem;
height: 2rem;
display: flex;
align-items: center;
box-sizing: border-box;
}
.mediaCard__buttonContainer--flip {
flex-direction: row-reverse;
}
.mediaCard__buttonIcon {
fill: currentColor;
margin: 0 0.5rem;
}
.mediaCard__actionButton {
cursor: pointer;
}
.mediaCard__fabContainer {
position: absolute;
}
.faq {
margin-bottom: 1rem;
}
.faq__question {
font-weight: bold;
}
<div style="padding: 1rem 0;">
Block Element Modifier (BEM):
{{link-to 'Demo' 'index' class="navButton"}}
{{link-to 'FAQ' 'faq' class="navButton"}}
</div>
{{outlet}}
{{title}}
{{#if actionButton}}
{{component actionButton}}
{{/if}}
<svg
version="1.1" xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink"
x="0px" y="0px"
width="24px" height="24px"
viewBox="0 0 24 24" enable-background="new 0 0 24 24" xml:space="preserve">
<g id="Bounding_Boxes">
<path fill="none" d="M0,0h24v24H0V0z"/>
</g>
<g id="Rounded">
<path d="M18.3,5.71L18.3,5.71c-0.39-0.39-1.02-0.39-1.41,0L12,10.59L7.11,5.7c-0.39-0.39-1.02-0.39-1.41,0l0,0
c-0.39,0.39-0.39,1.02,0,1.41L10.59,12L5.7,16.89c-0.39,0.39-0.39,1.02,0,1.41h0c0.39,0.39,1.02,0.39,1.41,0L12,13.41l4.89,4.89
c0.39,0.39,1.02,0.39,1.41,0l0,0c0.39-0.39,0.39-1.02,0-1.41L13.41,12l4.89-4.89C18.68,6.73,18.68,6.09,18.3,5.71z"/>
</g>
</svg>
<dl class="faq">
<dt class="faq__question">{{question}}</dt>
<dd>{{yield}}</dd>
</dl>
<svg version="1.1"
xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink"
x="0px" y="0px"
width={{baseSize}}
height={{baseSize}}
viewBox="0 0 24 24"
xml:space="preserve">
<circle cx="12" cy="12" r="6" fill="white"/>
<path fill="red" d="M12,2C6.48,2,2,6.48,2,12s4.48,10,10,10s10-4.48,10-10S17.52,2,12,2z M16,13h-3v3c0,0.55-0.45,1-1,1h0c-0.55,0-1-0.45-1-1v-3H8c-0.55,0-1-0.45-1-1v0c0-0.55,0.45-1,1-1h3V8c0-0.55,0.45-1,1-1h0c0.55,0,1,0.45,1,1v3h3c0.55,0,1,0.45,1,1v0C17,12.55,16.55,13,16,13z"/>
</svg>
{{#if showDescription}}
<div class="mediaCard__body">
{{card-title
title=title
actionButton='close-button'
click=(action 'toggleDescription')
}}
<p>
{{description}}
</p>
</div>
{{else}}
<div class="mediaCard__header {{if image 'mediaCard__header--image'}}" style={{headerBackground}}>
{{#unless bodyTitle}}
{{card-title
title=title
}}
{{/unless}}
</div>
<p class="mediaCard__body {{unless bodyTitle 'mediaCard__body--title'}}">
{{#if fab}}
<div class="mediaCard__fabContainer" style={{fabStyle}}>
{{floating-action-button largeFab=largeFab}}
</div>
{{/if}}
{{#if bodyTitle}}
{{card-title
title=title
actionButton=(if description 'more-button')
click=(action 'toggleDescription')
}}
{{/if}}
{{yield}}
</p>
{{#if links}}
<div class="mediaCard__links">
{{#each links as |link|}}
{{card-link name=link.name url=link.url}}
{{/each}}
</div>
{{/if}}
{{/if}}
<svg
version="1.1" xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink"
x="0px" y="0px"
width="24px" height="24px"
viewBox="0 0 24 24"
enable-background="new 0 0 24 24"
xml:space="preserve">
<g id="Bounding_Boxes">
<path fill="none" d="M0,0h24v24H0V0z"/>
</g>
<g id="Rounded">
<path d="M12,8c1.1,0,2-0.9,2-2s-0.9-2-2-2s-2,0.9-2,2S10.9,8,12,8z M12,10c-1.1,0-2,0.9-2,2s0.9,2,2,2s2-0.9,2-2S13.1,10,12,10z
M12,16c-1.1,0-2,0.9-2,2s0.9,2,2,2s2-0.9,2-2S13.1,16,12,16z"/>
</g>
</svg>
<div class="mediaCard__buttonContainer {{if iconFlip 'mediaCard__buttonContainer--flip'}}">
{{#if icon}}
<svg version="1.1"
class="mediaCard__buttonIcon"
xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink"
x="0px" y="0px"
width="24px" height="24px"
viewBox="0 0 24 24" enable-background="new 0 0 24 24" xml:space="preserve">
<g id="Bounding_Boxes">
<path fill="none" d="M0,0h24v24H0V0z"/>
</g>
<g id="Rounded">
<path d="M19.35,10.04C18.67,6.59,15.64,4,12,4C9.11,4,6.6,5.64,5.35,8.04C2.34,8.36,0,10.91,0,14c0,3.31,2.69,6,6,6h13c2.76,0,5-2.24,5-5C24,12.36,21.95,10.22,19.35,10.04z"/>
</g>
</svg>
{{/if}}
{{text}}
</div>
{{! Inline styles so we don't have to restyle this little helper }}
<code style="padding: 0 0.25rem; background-color: lightgray;">{{text}}</code>
{{#faq-item question='What is the impact to the client?'}}
1,362 bytes are added to the CSS sent to the client to account for all of the BEM classes.
{{/faq-item}}
{{#faq-item question='What is the impact to the developer?'}}
The cognitive load for this style is moderate.
{{/faq-item}}
{{#faq-item question="Okay, well don't we save bytes in the templates?"}}
In {{x-code 'media-card.hbs'}}, which is the largest template with the most styles, the BEM version is actually 22 bytes larger because of the length of the classnames. That doesn't carry through to everything. Some of the .js files are a bit shorter due to replacing functional classes. In general though, it seems pretty close to a wash for BEM.
{{/faq-item}}
{{#faq-item question='How well did this work with the component structure?'}}
That part was really easy! BEM pretty much follows along with a component structure, at least as I understand it. The Block seems to correspond to the main component, and the Elements with the sub-components. Modifiers work great for the options you pass into a component, like {{x-code 'dark=true'}}.
{{/faq-item}}
{{#faq-item question='What about side effects?'}}
If you leave everything component-scoped, then it is self-contained. But then what is the point of writing reusable classes? If you put the classes in global CSS then any changes made to them will have side-effects on consumers. If someone implements the styles but not the component (using their own markup which is probably different - that's an advantage of BEM) then we again enter the place where CSS changes can have unintended effects since we no longer have a single source of truth for how style combines with markup.
{{/faq-item}}
{{#faq-item question='Okay, what about reuse?'}}
I'll interpret this as 'reuse beyond an Ember component', because otherwise who cares what the styles look like. As mentioned above, BEM methodology uses classes only and they are not dependent on the HTML strucuture. This makes it somewhat reusable with different markup. I say somewhat because the E and M parts are semantically tied to the parent block. So it's all or nothing, as I understand it. You wouldn't use a {{x-code '.mediaCard__link'}} class in a {{x-code '.graph'}} block. So you are limited to applying some or all of {{x-code '.mediaCard'}} to some other HTML. Personally, I think components are a much better way to manage this. With namespaced components BEM is <em>no more or less reusable</em> than utility-first.
{{/faq-item}}
{{#faq-item question='Are you sure you implemented this correctly?'}}
Maybe? I could imagine splitting {{x-code 'media-card'}} into several blocks that would be more re-usable. In my interpretation though, the Block would be the highest level atomic component.
{{/faq-item}}
{{#faq-item question='What about razor templates?'}}
These classes can easily be reused anywhere you are willing to duplicate the HTML. Again, that's not DRY and I think components should be our direction.
{{/faq-item}}
{{#faq-item question='How do we deal with site customizations?'}}
We can use our current method of having each site provide such things in SASS variables.
{{/faq-item}}
<div style="padding: 0.5rem">
{{#media-card
title='Yog-Sothoth'
links=yogLinks
image='https://vignette.wikia.nocookie.net/lovecraft/images/f/fc/Yog_sothoth_rising_by_butttornado-d6ubvy6.jpg/revision/latest/scale-to-width-down/640?cb=20161222095032'
}}
Yog-Sothoth knows the gate. Yog-Sothoth is the gate. Yog-Sothoth is the key and guardian of the gate. Past, present, future, all are one in Yog-Sothoth. He knows where the Old Ones broke through of old, and where They shall break through again. He knows where They have trod earth's fields, and where They still tread them, and why no one can behold Them as They tread.
{{/media-card}}
<br/>
{{#media-card
title='Azathoth'
dark=true
links=yogLinks
}}
Outside the ordered universe [is] that amorphous blight of nethermost confusion which blasphemes and bubbles at the center of all infinity—the boundless daemon sultan Azathoth, whose name no lips dare speak aloud, and who gnaws hungrily in inconceivable, unlighted chambers beyond time and space amidst the muffled, maddening beating of vile drums and the thin monotonous whine of accursed flutes.
{{/media-card}}
<br/>
{{#media-card
title='Nyarlathotep'
fab=true
image='https://vignette.wikia.nocookie.net/lovecraft/images/0/00/Nyarlthotep.jpg/revision/latest/scale-to-width-down/391?cb=20150525013305'
}}
And through this revolting graveyard of the universe the muffled, maddening beating of drums, and thin, monotonous whine of blasphemous flutes from inconceivable, unlighted chambers beyond Time; the detestable pounding and piping whereunto dance slowly, awkwardly, and absurdly the gigantic, tenebrous ultimate gods — the blind, voiceless, mindless gargoyles whose soul is Nyarlathotep.
{{/media-card}}
<br/>
{{#media-card
title='Cthulhu'
fab=true
largeFab=true
bodyTitle=true
image='https://vignette.wikia.nocookie.net/lovecraft/images/9/9e/Cthulhu.png/revision/latest/scale-to-width-down/370?cb=20150909190114'
}}
A monster of vaguely anthropoid outline, but with an octopus-like head whose face was a mass of feelers, a scaly, rubbery-looking body, prodigious claws on hind and fore feet, and long, narrow wings behind.
{{/media-card}}
<br/>
{{#media-card
title='Hastur'
description=hasturDescription
bodyTitle=true
image='https://vignette.wikia.nocookie.net/lovecraft/images/a/af/Hastur.jpg/revision/latest/scale-to-width-down/390?cb=20150525022426'
}}
... after stumbling queerly upon the hellish and forbidden book of horrors the two learn, among other hideous things which no sane mortal should know, that this talisman is indeed the nameless Yellow Sign handed down from the accursed cult of Hastur—from primordial Carcosa, whereof the volume treats...
{{/media-card}}
<br/>
{{text-button text='Devour' icon=true}}
{{text-button text='Derange'}}
{{text-button text='Destroy' icon=true iconFlip=true}}
{{floating-action-button}}
</div>
{
"version": "0.15.0",
"EmberENV": {
"FEATURES": {}
},
"options": {
"use_pods": false,
"enable-testing": false
},
"dependencies": {
"jquery": "https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.js",
"ember": "3.1.3",
"ember-template-compiler": "3.1.3",
"ember-testing": "3.1.3"
},
"addons": {
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment