Skip to content

Instantly share code, notes, and snippets.

@ASH-Bryan
Last active September 10, 2018 15:02
Show Gist options
  • Save ASH-Bryan/9c6c09667547361383ee8aabcadf5d4a to your computer and use it in GitHub Desktop.
Save ASH-Bryan/9c6c09667547361383ee8aabcadf5d4a to your computer and use it in GitHub Desktop.
utility-theming
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', 'site-borderRadius'],
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', 'site-borderRadius', 'site-buttonColor']
});
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('demo')
});
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;
}
/*
Defines the site customization contract as utility classes.
*/
.site-linkColor {
color: green;
}
.site-borderRadius {
border-radius: 0.25rem;
}
.site-buttonColor {
background-color: navy;
}
/* This could be SASS variable or whatever */
:root {
--linkColor: red;
}
/* Global settings */
a {
color: var(--linkColor);
}
/* Utility class overrides from base */
.site-linkColor {
color: var(--linkColor);
}
.site-borderRadius {
border-radius: 1rem;
}
<div style="padding: 1rem 0;">
Themeing with utility classes:
{{link-to 'FAQ' 'index' class="navButton"}}
{{link-to 'Demo' 'demo' 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>
<p><a href="#">I'm just a plain link</a>, made red by a style directly applied to the anchor tag in the site file.</p>
<p><button class="site-linkColor">I'm a button</button> made red by using the utility class.</p>
<div style="padding: 0.5rem">
This card component is getting its border-radius from the utility class.
{{#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/>
These buttons are also getting their border-radius from the utility class, but their background color is from the base file. It isn't defined at the site level, which works fine:
{{text-button text='Devour' icon=true}}
{{text-button text='Derange'}}
{{text-button text='Destroy' icon=true iconFlip=true}}
</div>
<h2>The Problem (help me out here)</h2>
{{#faq-item question='Since theming is an <em>interface map</em> managed with SASS variables, new items require updates to site CSS to create the mapping.'}}
Pros:
<ul>
<li>Each site can maintain its own unique set of variables.</li>
</ul>
Cons:
<ul>
<li>Inconsistent variables and using component classes to hang them on mean new components will always require that mapping to be created.</li>
<li>The mapping must be at the site CSS level since SASS variables won't propogate across builds.</li>
</ul>
{{/faq-item}}
{{#faq-item question='Adding the mapping requires a UI build and in most cases a C# build.'}}
Pros:
<ul>
<li>??</li>
</ul>
Cons:
<ul>
<li>At least in FitCrew, C# builds take 10 minutes or more.</li>
<li>Also for FitCrew there are unit test and build requirements that must be run locally even for a CSS change.</li>
<li>Another source control location to deal with and files to copy manually.</li>
</ul>
{{/faq-item}}
<h2>The Solution?</h2>
{{#faq-item question='CSS custom properties would probably be ideal, but IE ruins that for us yet again.'}}
<img src="https://media.giphy.com/media/89bq55CYqPQDS/giphy.gif">
{{/faq-item}}
<p></p>
{{#faq-item question="Instead let's go simple - replace SASS variables with utility classes, and define a 'site customization interface' so we don't need to do a mapping."}}
<p>The utility classes are then added whenever we need to inherit a site's preference for color, border radius, padding, etc.</p>
Pros:
<ul>
<li>If we aren't adding a new customization type, <strong>no CSS updates are required</strong>.</li>
<li>Works across build boundaries, overrides are done using only the cascade.</li>
<li>The list of customizations is well-defined and can be managed by the CRAFT process.</li>
<li>Even if a component creator forgets to use a customization class, the fix is done by the creator, <strong>not the consumer</strong>.</li>
<li>It should be relatively easy to switch to custom properties when IE finally dies.</li>
</ul>
Cons:
<ul>
<li>Component creators must remember to use the customization classes.</li>
<li>There would be an up-front effort to create the initial list of customizations which all sites would have to agree on.</li>
<li>There is still some duplicated code since sites must override the variable defined in base.</li>
</ul>
{{/faq-item}}
{{#faq-item question="What if a new customization is added - how do we simplify the build process?"}}
<p>Megan has been working on a TFS build that deploys CSS to ashcompanies.com. Then C# web projects can just link to the asset. This removes the C# build step from the process!</p>
Pros:
<ul>
<li>CSS updates require only 1 build (assuming builds can trigger other builds?) which is fast and doesn't require unnecessary unit tests and local builds.</li>
<li>This would bring CSS management more in-line with Ember releases and more under FE control.</li>
</ul>
Cons:
<ul>
<li>There might be some worry about source management - but it's really no different than how we do Ember now. We can still deploy old releases if needed.</li>
</ul>
{{/faq-item}}
<h2>Old Process (new customization or not)</h2>
<ol>
<li>Creator makes the component, including a SASS partial for base that does the variable mapping.</li>
<li>Consumer includes the component.</li>
<li>Consumer must create their own partial for variable mapping. This isn't a no-brainer since all sites have different variables.</li>
<li>Consumer does a build for CSS and a C# build to promote the CSS changes.</li>
<li>If there are any styling gaps, it's likely that changes will be required on both sides.</li>
</ol>
<h2>New Process (no new customization)</h2>
<ol>
<li>Creator makes the component, using utility classes from the customization interface.</li>
<li>Consumer includes the component.</li>
<li><strong>That's it.</strong> No CSS changes or builds required.</li>
</ol>
<h2>New Process (new customization)</h2>
<ol>
<li>Creator makes the component, using utility classes from the customization interface.</li>
<li>Creator uses the CRAFT process to have a new customization added to the interface.</li>
<li>Consumers can go ahead and implement the new utilities at this time, in advance of component release, or do nothing if they are fine with the base value. If they implement there is just 1 build.</li>
<li>Consumer includes the component.</li>
</ol>
<h2>Next Steps</h2>
<ol>
<li>Start the CRAFT process of determining what the available site customizations should be.</li>
<li>Establish the initial base file and the site files.</li>
<li>Convert Ember components one at a time - this migration should actually reduce our CSS size a bit as we remove partials. This will also help identify customizations we missed.</li>
<li>Consider whether to convert static HTML now, or as it moves to components.</li>
</ol>
<h2>The Problem (help me out here)</h2>
{{#faq-item question='Since theming is an <em>interface map</em> managed with SASS variables, new items require updates to site CSS to create the mapping.'}}
Pros:
<ul>
<li>Each site can maintain its own unique set of variables.</li>
</ul>
Cons:
<ul>
<li>Inconsistent variables and using component classes to hang them on mean new components will always require that mapping to be created.</li>
<li>The mapping must be at the site CSS level since SASS variables won't propogate across builds.</li>
</ul>
{{/faq-item}}
{{#faq-item question='Adding the mapping requires a UI build and in most cases a C# build.'}}
Pros:
<ul>
<li>??</li>
</ul>
Cons:
<ul>
<li>At least in FitCrew, C# builds take 10 minutes or more.</li>
<li>Also for FitCrew there are unit test and build requirements that must be run locally even for a CSS change.</li>
<li>Another source control location to deal with and files to copy manually.</li>
</ul>
{{/faq-item}}
<h2>The Solution?</h2>
{{#faq-item question='CSS custom properties would probably be ideal, but IE ruins that for us yet again.'}}
<img src="https://media.giphy.com/media/PTSjtSIkx7aqQ/giphy.gif">
{{/faq-item}}
<p></p>
{{#faq-item question="Instead let's go simple - replace SASS variables with utility classes, and define a 'site customization interface' so we don't need to do a mapping."}}
<p>The utility classes are then added whenever we need to inherit a site's preference for color, border radius, padding, etc.</p>
Pros:
<ul>
<li>If we aren't adding a new customization type, <strong>no CSS updates are required</strong>.</li>
<li>Works across build boundaries, overrides are done using only the cascade.</li>
<li>The list of customizations is well-defined and can be managed by the CRAFT process.</li>
<li>Even if a component creator forgets to use a customization class, the fix is done by the creator, <strong>not the consumer</strong>.</li>
<li>It should be relatively easy to switch to custom properties when IE finally dies.</li>
</ul>
Cons:
<ul>
<li>Component creators must remember to use the customization classes.</li>
<li>There would be an up-front effort to create the initial list of customizations which all sites would have to agree on.</li>
<li>There is still some duplicated code since sites must override the variable defined in base.</li>
</ul>
{{/faq-item}}
{{#faq-item question="What if a new customization is added - how do we simplify the build process?"}}
<p>Megan has been working on a TFS build that deploys CSS to ashcompanies.com. Then C# web projects can just link to the asset. This removes the C# build step from the process!</p>
Pros:
<ul>
<li>CSS updates require only 1 build (assuming builds can trigger other builds?) which is fast and doesn't require unnecessary unit tests and local builds.</li>
<li>This would bring CSS management more in-line with Ember releases and more under FE control.</li>
</ul>
Cons:
<ul>
<li>There might be some worry about source management - but it's really no different than how we do Ember now. We can still deploy old releases if needed.</li>
</ul>
{{/faq-item}}
<h2>Old Process (new customization or not)</h2>
<ol>
<li>Creator makes the component, including a SASS partial for base that does the variable mapping.</li>
<li>Consumer includes the component.</li>
<li>Consumer must create their own partial for variable mapping. This isn't a no-brainer since all sites have different variables.</li>
<li>Consumer does a build for CSS and a C# build to promote the CSS changes.</li>
<li>If there are any styling gaps, it's likely that changes will be required on both sides.</li>
</ol>
<h2>New Process (no new customization)</h2>
<ol>
<li>Creator makes the component, using utility classes from the customization interface.</li>
<li>Consumer includes the component.</li>
<li>If there are styling gaps, those are fixed by the creator.</li>
<li><strong>That's it.</strong> No CSS changes or builds required.</li>
</ol>
<h2>New Process (new customization)</h2>
<ol>
<li>Creator makes the component, using utility classes from the customization interface but identifies a need for a new customization.</li>
<li>Creator uses the CRAFT process to have a new customization agreed upon and added to the interface.</li>
<li>Consumers can go ahead and implement the new utility class at this time, in advance of component release.</li>
<li>Consumer includes the component.</li>
</ol>
<h2>Next Steps</h2>
<ol>
<li>Start the CRAFT process of determining what the available site customizations should be.</li>
<li>Establish the initial base file and the site files.</li>
<li>Convert Ember components one at a time - this migration should actually reduce our CSS size a bit as we remove partials. This will also help identify customizations we missed.</li>
<li>Consider whether to convert static HTML now, or as it moves to components.</li>
</ol>
{
"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