Skip to content

Cell Query

esr360 edited this page Feb 16, 2020 · 3 revisions

Introduction

Cell implements Cell Query (CQ), which is an abstraction of Cell's mixins' behaviour in the form of plain keys and values, allowing them to be authorable in most programming languages as objects (or "maps" in Sass).

There are two main ways to utilise CQ within Cell:

1. Using $content Parameter (Out-The-Box)

The first way is by passing a CQ compatible Sass map to the $content parameter of either the Module or Component mixins. These maps will automatically be parsed as CQ.

2. Using Configuration (Opt-In)

CQ is ideal to store in your module's configuration, where Cell can read the configuration and subsequently parse any CQ as CSS. This is an opt-in feature, and can be used in conjunction with the previous $content method. To opt-in to this feature, the $outputCSSFromConfig Global Variable should be set to true.

This feature gives you more flexibility and hence control over authoring the styles for your modules. The purpose isn't to simply allow you to author all of your styles as objects (like with the previous $content method), but is rather to allow you to abstract configurable CSS properties from your source code and keep them separate as configutation. In short, if a CSS property is prone to changing for cosmetic purposes, it can be considered "configurable" and can be kept inside the module's configuration, instead of within the module's source code.

Consider the following example of a hypothetical header module:

@include module('header') {
  display: 'block';
  position: 'absolute';
  top: 0;
  width: 100%;
  background-color: #444444;
  height: 100px;
}

If we wanted to make the background-color and height properties configurable, we might think to do something like this, like many other UI frameworks:

$header-background: #444444 !default;
$header-height: 100px !default;

@include module('header') {
  display: 'block';
  position: 'absolute';
  top: 0;
  width: 100%;
  background-color: $header-background;
  height: $header-height;
}

...i.e extract the values away from the source code and into variables. This is good as it allows you to keep all configurable values in one place. Using Cell's utilities, the same thing could be achieved in a slightly different flavour:

$config: (
  'background-color': #444444,
  'height': 100px
);

@include module('header') {
  display: 'block';
  position: 'absolute';
  top: 0;
  width: 100%;
  background-color: this('background-color');
  height: this('height');
}

Cell can take this concept one step further; if a configuration key (e.g. background-color) just so happens to correspond to a valid CSS property, Cell will save you the trouble of having to hard-code that property into your souce code, leaving us with:

$config: (
  'background-color': #444444,
  'height': 100px
);

@include module('header') {
  display: 'block';
  position: 'absolute';
  top: 0;
  width: 100%;
}

This keeps the source code of your modules cleaner by housing only fundamental CSS properties that control the module's structure and functionality, abstracting any cosmetic CSS properties into configuration. The theory is that without a module's configuration you would still be left with a functioning interactive module that didn't "break" anything (i.e. no unwanted side effects).

One of the biggest risks when changing a CSS codebase is unknown or unwanted side effects, and when you're changing properties like display and position (i.e structural/layout properties), it's much easier to break things, which is why it makes sense to separate such properties.

Distinguising between CSS properties in these terms grants the power of changing the cosmetic look and feel of your project without writing any code, and with little to no risk of unwanted side effects.

Check out the CSS at Scale: Cosmetic vs Layout Properties article for more information on this concept

Combined with the CQ API, this allows you to style your modules like a superhero.

The CQ API

See the following sections to learn how each Cell mixin can be represented as CQ:

Component Mixin

See the Component() page for more information on this mixin

The Component mixin is represented in CQ as:

'{COMPONENT}': {
  ...
}
Alternatively
'__{COMPONENT}': {
  ...
}
Alternatively
'component({COMPONENT})': {
  ...
}
Equivalent Sass
@include component(#{COMPONENT}) {
  ...
}

Example

$config: (
  'name': 'myModule',
  'myComponent': (
    'color': red
  )
);

@include module {
  @include component('myComponent') {
    display: block;
  }
}
CSS Output
.myModule__myComponent, [class*="myModule__myComponent--"] {
  color: red;
}

.myModule__myComponent, [class*="myModule__myComponent--"] {
  display: block;
}

Is/Modifier Mixin

See the Modifier() page for more information on this mixin

The Modifier mixin is represented in CQ as:

'is-{MODIFIER}': {
  ...
}
Alternatively
'--{MODIFIER}': {
  ...
}
Alternatively
'modifier({MODIFIER})': {
  ...
}
Equivalent Sass
@include is(#{MODIFIER}) {
  ...
}

Example

$config: (
  'name': 'myModule',
  'scale-amount': 0.75,
  'is-active': (
    'background': red
  )
);

@include module {
  transform: scale(this('scale-amount'));

  @include is('active') {
    transform: scale(1);
  }
}
CSS Output
.myModule, [class*="myModule--"] {
  transform: scale(0.75);
}

[class*="myModule--"][class*="--active"] {
  background: red;
}

[class*="myModule--"][class*="--active"] {
  transform: scale(1);
}

Context Mixin

See the Context() page for more information on this mixin

The Context mixin can be represented in CQ in several ways depending on the desired behaviour.

Style element when it is a child of the specified component

'in-{COMPONENT}': {
  ...
}
Equivalent Sass
@include context(($this, #{COMPONENT})) {
  ...
}
Example
$config: (
  'name': 'post',
  'heading': (
    'in-tile': (
      'margin-left': 1em
    )
  )
);

@include module {
  display: inline-block;
}
CSS Output
.post, [class*="post--"] {
  display: inline-block;
}

[class*="post__"][class*="__tile"] .post__heading, 
[class*="post__"][class*="__tile"] [class*="post__heading--"] {
  margin-left: 1em;
}

Style element when it is a child of the specified module

'in-${MODULE}': {
  ...
}
Equivalent Sass
@include context(#{MODULE}) {
  ...
}
Example
$config: (
  'name': 'icon',
  'in-$button': (
    'margin-right': 1em
  )
);

@include module {
  display: inline-block;
}
CSS Output
.icon, [class*="icon--"] {
  display: inline-block;
}

.button .icon, 
.button [class*="icon--"], 
[class*="button--"] .icon, 
[class*="button--"] [class*="icon--"] {
  margin-right: 1em;
}

Style element when parent module has specified modifier/pseudo-state

'$-is-{MODIFIER}': {
  ...
}
'$:{PSEUDO-STATE}': {
  ...
}
Equivalent Sass
@include context($this, #{CONTEXT}) {
  ...
}
Example
$config: (
  'name': 'myModule',
  'myComponent': (
    '$-is-highlight': (
      'background': limegreen
    ),
    '$:hover': (
      'background': dodgerblue
    )
  )
);

@include module {
  display: block;
}
CSS Output
.myModule, 
[class*="myModule--"] {
  display: block;
}

[class*="myModule--"][class*="--highlight"] .myModule__myComponent, 
[class*="myModule--"][class*="--highlight"] [class*="myModule__myComponent--"] {
  background: limegreen;
}

.myModule:hover .myModule__myComponent, 
.myModule:hover [class*="myModule__myComponent--"], 
[class*="myModule--"]:hover .myModule__myComponent, 
[class*="myModule--"]:hover [class*="myModule__myComponent--"] {
  background: dodgerblue;
}

Style element when specified parent component has specified modifier/pseudo-state

'{COMPONENT}-is-{CONTEXT}': {
  ...
}
Equivalent Sass
@include context(#{COMPONENT}, #{CONTEXT}) {
  ...
}
Example
$config: (
  'name': 'myModule',
  'title': (
    'panel-is-active': (
      'background': limegreen
    ),
    'panel-is-:hover': (
      'background': dodgerblue
    )
  )
);

@include module {
  display: block;
}
CSS Output
.myModule, 
[class*="myModule--"] {
  display: block;
}

[class*="myModule__panel--"][class*="--active"] .myModule__title, 
[class*="myModule__panel--"][class*="--active"] [class*="myModule__title--"] {
  background: limegreen;
}

.myModule__panel:hover .myModule__title, 
.myModule__panel:hover [class*="myModule__title--"] {
  background: dodgerblue;
}

[class*="myModule__panel--"]:hover .myModule__title, 
[class*="myModule__panel--"]:hover [class*="myModule__title--"] {
  background: dodgerblue;
}

Nesting/Chaining Contexts (Modifiers/Pseudo-States)

'and-is-{MODIFIER}': {
  ...
}
'and:{PSEUDO-STATE}': {
  ...
}

Sub-Component Mixin

See the Component() page for more information on this mixin

The Sub-Component mixin is represented in CQ as:

'{COMPONENT}': {
  '{SUB-COMPONENT}': {
    ...
  }
}
Equivalent Sass
@include component(#{COMPONENT}) {
  @include sub-component(#{SUB-COMPONENT}) {
    ...
  }
}

Example

$config: (
  'name': 'header',
  'navigation': (
    'item': (
      padding: 1em
    )
  )
);

@include module {
  @include component('navigation') {
    @include sub-component('item') {
      display: inline-block;
    }
  }
}
CSS Output
.header__navigation__item, 
[class*="header__navigation__item--"] {
  padding: 1em;
}

.header__navigation__item, 
[class*="header__navigation__item--"] {
  display: inline-block;
}

Pseudo-State/Pseudo-Element Mixin

See the Pseudo-State() page for more information on this mixin

The Pseudo-State mixin is represented in CQ as:

':{PSEUDO-STATE}': {
  ...
}
Equivalent Sass
@include pseudo-state(#{PSEUDO-STATE}) {
  ...
}
Equivalent To
&:#{PSEUDO-STATE} {
  ...
}

Example

$config: (
  'name': 'myModule',
  ':hover': (
    'background': #0155a0
  )
);

@include module {
  display: block;
}
CSS Output
.myModule, 
[class*="myModule--"] {
  display: block;
}

.myModule:hover, 
[class*="myModule--"]:hover {
  background: #0155a0;
}

Complete Example

The below example shows how Cell can be used to style a UI accordion, keeping all configurable cosmetic properties separate from the functional/layout properties, allowing them to be easily overridden with different themes etc.

Accordion Markup
<div class="accordion">
  <div class="accordion__panel">
    <div class="accordion__title">Accordion title 1</div>
    <div class="accordion__content">
      ...
    </div>
  </div>
  <div class="accordion__panel--active">
    <div class="accordion__title">Accordion title 2</div>
    <div class="accordion__content">
      ...
    </div>
  </div>
</div>
Accordion Styles
$config: (
  'name': 'accordion',

  'title': (
    'padding': 1em,
    'color': #ffffff
    'background': #0155a0,

    ':hover': (
      'background': #1cb1db
    ),

    'panel-is-active': (
      'background': #f21550
    )
  )
);

@include module {
  @include component('panel') {
    display: block;
  }

  @include component('title') {
    cursor: pointer;
  }

  @include component('content') {
    display: none;

    @include context(($this, 'panel'), 'active') {
      display: block;
    }
  }
}
CSS Output
.accordion__title, 
[class*="accordion__title--"] {
  padding: 1em;
  color: #ffffff;
  background: #0155a0;
}

.accordion__title:hover, 
[class*="accordion__title--"]:hover {
  background: #1cb1db;
}

[class*="accordion__panel--"][class*="--active"] .accordion__title, 
[class*="accordion__panel--"][class*="--active"] [class*="accordion__title--"] {
  background: #f21550;
}

.accordion__content, 
[class*="accordion__content--"] {
  background: #ffffff;
}

.accordion__panel, 
[class*="accordion__panel--"] {
  display: block;
}

.accordion__title, 
[class*="accordion__title--"] {
  cursor: pointer;
}

.accordion__content, 
[class*="accordion__content--"] {
  display: none;
}

[class*="accordion__panel--"][class*="--active"] .accordion__content, 
[class*="accordion__panel--"][class*="--active"] [class*="accordion__content--"] {
  display: block;
}
Clone this wiki locally