Skip to content

Instantly share code, notes, and snippets.

@samselikoff
Last active June 25, 2019 01:33
Show Gist options
  • Select an option

  • Save samselikoff/7b4b4a96b24dfa3e0606340a88eaa95f to your computer and use it in GitHub Desktop.

Select an option

Save samselikoff/7b4b4a96b24dfa3e0606340a88eaa95f to your computer and use it in GitHub Desktop.

Revisions

  1. samselikoff revised this gist Oct 16, 2018. No changes.
  2. samselikoff revised this gist Oct 16, 2018. No changes.
  3. samselikoff created this gist Jul 28, 2018.
    6 changes: 6 additions & 0 deletions lib-style-group.js
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,6 @@
    export default class StyleGroup {
    constructor(styles) {
    this.styles = styles;
    this.name = ''; // must set at runtime
    }
    }
    222 changes: 222 additions & 0 deletions mixins-styled.js
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,222 @@
    import Mixin from '@ember/object/mixin';
    import StyleGroup from 'ember-cli-ui-components/lib/style-group';
    import { assign } from '@ember/polyfills';
    import { assert } from '@ember/debug';
    import { computed } from '@ember/object';

    /*
    The computed classes are stored in the `activeClasses` property. By default they
    are applied to the root element.
    If you don't want them to, set
    applyActiveClassesToRoot: false
    and use them in your template
    <div class='mr4'>
    <p class={{activeClasses}}>{{yield}}</p>
    </div>
    */
    export default Mixin.create({

    styles: {},
    applyActiveClassesToRoot: true,
    style: '',

    init() {
    this._super(...arguments);
    this._setStyleGroupNames();
    this._validateDefaultStyle();
    this._setTagName();

    if (this.get('tagName') !== '' && this.get('applyActiveClassesToRoot')) {
    this.classNameBindings = this.classNameBindings.slice();
    this.classNameBindings.push('activeClasses');
    }
    },

    activeStyles: computed('style', function() {
    let activeStyles = (this.get('styles.defaultStyle') || '').split(/\s/);
    let externalactiveStyles = (this.get('style') || '').split(/\s/);

    this._validateStyles(externalactiveStyles);

    externalactiveStyles.forEach(style => {
    let styleGroup = this._getStyleGroupForStyle(style);
    if (styleGroup) {
    let match = this._checkListForStyleFromGroup(activeStyles, styleGroup);
    if (match) {
    activeStyles.splice(activeStyles.indexOf(match), 1, style);
    } else {
    activeStyles.push(style);
    }
    } else {
    activeStyles.push(style);
    }
    })

    return activeStyles;
    }),

    activeClasses: computed('activeStyles', function() {
    let baseClasses = this._getBaseClasses();
    let styleClasses = this._getStyleClasses();

    return baseClasses.concat(styleClasses)
    .filter(el => !!el)
    .join(' ');
    }),

    // Private
    _getBaseClasses() {
    let baseClasses = this.get('styles.base') || '';
    return baseClasses.split(/\s/);
    },

    _getStyleClasses() {
    let styleDefinitions = this._getStyleDefinitions();

    return this.get('activeStyles')
    .map(name => styleDefinitions[name])
    .filter(definition => definition !== undefined)
    .map(definition => {
    let classes;

    if (typeof definition === 'string') {
    classes = definition;
    } else {
    classes = definition.style;
    }

    return classes;
    });
    },

    /*
    Return flat object of styles (top-level and groups)
    */
    _getStyleDefinitions() {
    let styles = this.get('styles') || {};

    return Object.keys(styles).reduce((allStyles, key) => {
    let newStyle;

    if (styles[key] instanceof StyleGroup) {
    newStyle = styles[key].styles
    } else {
    newStyle = { [key]: styles[key] };
    }

    Object.keys(newStyle).forEach(key => {
    assert(`Styled: You defined two styles named '${key}' on '${this._debugContainerKey}'. Stylenames must be unique across all groups.`, allStyles[key] === undefined);
    });

    return assign({}, allStyles, newStyle);
    }, {});
    },

    _getActiveStyleDefinitions() {
    let definitions = this._getStyleDefinitions();
    let activeStyles = this.get('activeStyles');

    return Object.keys(definitions)
    .filter(style => activeStyles.includes(style))
    .reduce((hash, style) => {
    hash[style] = definitions[style];

    return hash;
    }, {});
    },

    _setStyleGroupNames() {
    Object.keys(this.get('styles') || [])
    .forEach(key => {
    let definition = this.get(`styles.${key}`);
    if (definition instanceof StyleGroup) {
    definition.name = key;
    }
    });
    },

    _styleGroups() {
    return Object.keys(this.get('styles'))
    .map(key => this.get(`styles.${key}`))
    .filter(defn => defn instanceof StyleGroup);
    },

    _validateDefaultStyle() {
    let defaultStyle = this.get('styles.defaultStyle');

    if (defaultStyle) {

    defaultStyle.split(' ')
    .filter(Boolean)
    .forEach(style => {
    assert(
    `Styled: You set a default style named '${style}' on '${this._debugContainerKey}', but that style was not defined.`,
    this._styleExists(style)
    );
    });
    }
    },

    _styleExists(style) {
    let allStyleKeys = Object.keys(this._getStyleDefinitions());

    return allStyleKeys.includes(style);
    },

    _validateStyles(activeStyles) {
    let styleGroups = this._styleGroups();
    let styleGroupsUsed = [];

    activeStyles.filter(Boolean).forEach(style => {
    // Verify every active style has a definition
    assert(
    `Styled: You're using a style named '${style}' on '${this._debugContainerKey}', but that style was not defined.`,
    this._styleExists(style)
    );

    // Verify multiple styles from the same group are not being used
    styleGroups.forEach(styleGroup => {
    let stylesInGroup = Object.keys(styleGroup.styles);

    if (stylesInGroup.includes(style)) {
    assert(
    `Styled: You passed the '${style}' style to a ${this._debugContainerKey} but you've already used a style from the '${styleGroup.name}' oneOf group.`,
    !styleGroupsUsed.includes(styleGroup.name)
    );
    styleGroupsUsed.push(styleGroup.name);
    }
    });
    });
    },

    _getStyleGroupForStyle(style) {
    return this._styleGroups().find(styleGroup => {
    return Object.keys(styleGroup.styles).includes(style);
    });
    },

    _checkListForStyleFromGroup(list, styleGroup) {
    let stylesFromGroup = Object.keys(styleGroup.styles);

    return list.find(style => {
    return stylesFromGroup.includes(style);
    });
    },

    _setTagName() {
    let activeDefinitions = this._getActiveStyleDefinitions();
    let styleDidSetTagName;

    Object.keys(activeDefinitions)
    .forEach(style => {
    let definition = activeDefinitions[style];

    if (definition.tagName) {
    assert(`You're rendering a '${this._debugContainerKey}' with an active style of '${style}' that's setting the tagName, but the '${styleDidSetTagName}' style is already active and also setting the tagName.`, !styleDidSetTagName);
    styleDidSetTagName = style;

    this.set('tagName', definition.tagName);
    }
    });
    }
    });
    74 changes: 74 additions & 0 deletions ui-button.js
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,74 @@
    import Component from '@ember/component';
    import { Styled, group } from 'ember-cli-ui-components';

    export default Component.extend(Styled, {

    tagName: 'button',

    styles: {
    base: 'leading-tight pointer relative transition',

    defaultStyle: 'inline-block medium gray dim margins round',

    colors: group({
    gray: 'bg-black-10 text-black-80 font-medium',
    subtle: 'bg-black-10 text-black-40 font-medium',
    brand: 'border-none bg-brand-gradient text-white font-medium',
    warn: 'border-none bg-dark-red text-white font-semibold',
    white: 'font-normal bg-transparent border-solid border-2 border-white text-white',
    blue: 'border-none bg-blue text-white',
    'white-bg': 'font-normal bg-white text-near-black',
    }),

    active: 'bg-light-red text-white',

    sizes: group({
    small: 'text-7 xs:text-6 py-1 xs:py-2 px-2 xs:px-3',
    medium: 'text-6 xs:text-4 py-2 xs:py-3 px-3 xs:px-4',
    large: 'text-5 xs:text-4 py-3 px-3 xs:px-4'
    }),

    'nowrap': 'whitespace-no-wrap',

    floating: 'shadow-l',

    behavior: group({
    dim: 'dim',
    disabled: 'opacity-50 no-events'
    }),

    margins: group({
    margins: 'mt-2 mb-3',
    marginless: ''
    }),

    uppercase: 'uppercase',

    radii: group({
    round: 'rounded-2',
    pill: 'rounded-pill',
    append: 'rounded-r'
    }),

    bold: 'font-bold',

    full: 'w-full',

    displays: group({
    block: 'block',
    'inline-block': 'inline-block',
    flex: 'flex'
    }),

    input: {
    tagName: 'input'
    },

    link: {
    style: 'no-underline',
    tagName: 'a'
    }
    }

    });