Skip to content

Instantly share code, notes, and snippets.

@ASH-Bryan
Last active August 22, 2018 23:31
Show Gist options
  • Save ASH-Bryan/7d930beb31be65d0a6b5eac6fd2b8c7a to your computer and use it in GitHub Desktop.
Save ASH-Bryan/7d930beb31be65d0a6b5eac6fd2b8c7a to your computer and use it in GitHub Desktop.
css-utility-first
import Ember from 'ember';
export default Ember.Component.extend({
tagName: 'h1',
classNames: ['flex', 'justify-between']
});
import Ember from 'ember';
export default Ember.Component.extend({
classNames: ['cursor-pointer']
});
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: ['cursor-pointer'],
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: ['shadow', 'rounded'],
classNameBindings: ['dark:bg-grey-darker', 'dark:text-white'],
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: -2rem; right: 1rem;')
: htmlSafe('top: -1.5rem; right: 1rem;')
}),
actions: {
toggleDescription() {
this.toggleProperty('showDescription')
}
}
});
import Ember from 'ember';
export default Ember.Component.extend({
classNames: ['cursor-pointer']
})
import Ember from 'ember';
export default Ember.Component.extend({
tagName: 'button',
classNames: ['text-white', 'bg-teal', 'rounded', 'flex']
});
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;
//margin: 1rem 0;
}
.navButton.active {
color: white;
background-color: blue;
}
<div style="padding: 1rem 0;">
Utility-first functional CSS via Tailwind:
{{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="mb-4">
<dt class="font-bold">{{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="p-3">
{{card-title
title=title
actionButton='close-button'
click=(action 'toggleDescription')
}}
<p class="pt-2">
{{description}}
</p>
</div>
{{else}}
<div class="flex p-3 items-end {{if image 'h-48'}} {{if image 'text-white'}}" style={{headerBackground}}>
{{#unless bodyTitle}}
{{card-title
title=title
}}
{{/unless}}
</div>
<p class="relative p-3 {{unless bodyTitle 'pt-8'}}">
{{#if fab}}
<div class="absolute" 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="p-3 border-t mt-2 pt-2">
{{#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="p-2 h-8 flex items-center {{if iconFlip 'flex-row-reverse'}}">
{{#if icon}}
<svg version="1.1" class="fill-current mx-2"
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='Why did you write this as an Ember component?'}}
I think we should prioritize our CSS choice by where we <em>want to go</em> over <em>where we are coming from</em>.
{{/faq-item}}
{{#faq-item question='Was it helpful in some way to write this as components?'}}
Yes it was, and very astute of you to ask! If you think about it, the goals for CSS and software are very much aligned - reusable, DRY code. Writing {{x-code 'media-card'}} as components naturally split it along reusable boundaries. Using Tailwind, if I ever was tempted to copy and paste most or all of a combination of functional classes, that was a clear indicator that a component was needed.
{{/faq-item}}
{{#faq-item question='What is the impact to the client?'}}
0 bytes are added to the CSS sent to the client (assumes you've already eaten the utility CSS size).
{{/faq-item}}
{{#faq-item question='What is the impact to the developer?'}}
The cognitive load for this style is minimal.
{{/faq-item}}
{{#faq-item question='What about side effects?'}}
Everything is self-contained. There are no classes to leak out, and other CSS can't unintentionally modify this one's appearance.
{{/faq-item}}
{{#faq-item question='Okay, what about reuse?'}}
The media-card component is completely reusable since it's an Ember component. The difference is that all of the complexity is inside the component, instead of some being in the component and some in one or more separate CSS files. The complexity has to go somewhere. Note that this is the same case for all of the other styles if we are using namespaced components - <em>utility-first is no more or less reusable</em>.
{{/faq-item}}
{{#faq-item question='Great for Ember, but what about razor templates?'}}
First I think we should consider how long we intend to support non-Ember pages. A couple years? Forever? If it's forever then we still have a good option. Check this out:
<p>{{x-code 'ember new media-card -b @glimmer/blueprint --web-component'}}</p>
That's right, Glimmer can compile to web components and they are even supported in IE11 with a polyfill. Even if we decide to support razor templates indefinitely, we could use server-side templates. I think our goal as front end should be to create components, not more static HTML.
{{/faq-item}}
{{#faq-item question='How do we deal with site customizations?'}}
We can define a specific set of utilities that each site overrides for things like colors and border radii. A benefit of this is it would force us to create what is essentially an interface contract that our sites adhere to.
{{/faq-item}}
{{#faq-item question='Why go through such a major change in philosophy when we could keep things the same and just enforce more consistency through naming/structure conventions?'}}
Imagine being able to focus on simply creating reusable components without worrying about checking base styles for something that might be close to what you need and then trying to modify it in the least offensive way. Failing that, writing a bunch of new classes that <em>will never leave that component</em> (since we are scoping to components) but will still bloat our CSS files.
{{/faq-item}}
{{#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}}
{
"version": "0.15.0",
"EmberENV": {
"FEATURES": {}
},
"options": {
"use_pods": false,
"enable-testing": false
},
"dependencies": {
"tailwind": "https://cdn.jsdelivr.net/npm/tailwindcss/dist/tailwind.min.css",
"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