@sencha/ext-modern-theme-base
v7.8.0
Published
Ext JS Modern Base Theme
Downloads
470
Readme
#Ext JS Modern Toolkit Theming Guidelines
Dynamic Variables
All variables should be defined using the dynamic()
function. This ensures
that the last variable declaration wins, and is valid throughout the entire
scope, allowing variables to derive from one another without concern for
ordering.
File Naming
All Component variable and UI mixin declarations (all non-rule-generating
code) should be placed in the theme's sass/var/
directory in a file matching
the Component's class path. Since all var files are included in the build
regardless of whether or not the corresponding class is actually required
by the app, this allows variables to freely derive from any other variable.
Including mixins in the sass/var/
directory ensures that a derived theme can
override those mixins before they are called by any code in the sass/src/
directory.
All rule-generating code, including calls to UI mixins, and rules not contained
inside the body of a mixin should be placed in the theme's sass/src/
directory in a file matching the Component's class path. At build time,
src files are only included if the corresponding Component is required by
the app, thus ensuring that only the rules that the app actually needs are
included in the CSS output.
Base Styles vs. Configurable Styles
Layout-specific styles, and styles related to core functionality of a
Component should always be placed in theme-base/sass/src/
in a file
matching the Component's class path. The properties set in these rules
should NOT be configurable using variables, as changing them would likely
break the functionality or layout of the Component. Some examples of
CSS properties that should generally not be configurable are:
display
visibility
position
overflow
z-index
Configurable styles not related to the core functionality or layout
of a component should always be controlled using variables. These
variables should be defined in a scss file matching the component's class
path in the theme-neptune/sass/var
directory, and the rules that use
these variables should be contained in a UI mixin in the same file.
Examples of commonly configurable styles are:
background-color
color
padding
border-radius
font-size
The neptune theme is the base for all other themes and should contain
the universe of possible theming capabilities supported by the framework
although it may not utilize them all itself. Themes derived from theme-neptune
should avoid defining new UI mixins and creating their own CSS rules, but
should instead simply set the values of variables defined in theme-neptune
.
When a new feature is needed by a derived theme it should be added to
theme-neptune
with variables to tune its behavior rather than adding
the new feature only to the derived theme.
##Variable Naming Conventions
Component variables should always begin with an xtype, and end with the CSS property name being styled, for example:
$button-font-family: dynamic(helvetica);
$button-color: dynamic(#fff);
$button-background-color: dynamic(red);
If the component has various states such as hovered, focused, and pressed, the name of the state should come immediately after the xtype, but before the CSS property name:
$button-hovered-background-color: dynamic(blue);
If the component has variables that control the styling of sub-elements, the name of the sub-element being styled should be included after the xtype, and state if present. For example, when styling the button's "badge" element:
$button-badge-color: dynamic(#fff);
$button-hovered-badge-color: dynamic(green);
If however, the "state" refers to a sub-element's state, it should come after that element's name. For example, if a tab has a close icon that has a separate hover state from the tab:
$tab-close-icon-hovered-background-color: dynamic(red);
Components should have separate variables for border-width, border-color, and border-style, and all three properties should accept either a single value or a list of values so that 4 sides can be specified separately if needed:
// BAD
$button-border: dynamic(1px solid green);
// GOOD
$button-border-color: dynamic(red yellow green blue);
$button-border-width: dynamic(1px 3px);
$button-border-style: dynamic(solid);
Variables should use the following names to indicate component state:
- "pressed" when the component is being pressed by the user or is in a pressed state
- "pressing" if the component has a "pressing" state that is separate from "pressed"
- "hovered" when the mouse pointer is over the element
- "focused" when the element has focus
- "disabled" when the component is disabled.
Since "focused" can sometimes be combined with other states components may
need to provide variables that indicate a combination of states,
for exmaple $button-focused-pressed-border-color
Normal and Big Modes
Each theme has 2 modes of sizing - normal and big. Big mode increases
spacing and sizing to be more touch-screen friendly. Themes select whether
or not to use big mode by inspecting Ext.platformTags
in the theme's
overrides/init.js
and adding a CSS class name of x-big
to the <html>
element on the page. For example, the triton theme only enables big mode
when loaded on a phone and uses normal mode otherwise:
Ext.theme.getDocCls = function() {
return Ext.platformTags.phone ? 'x-big' : '';
};
All variables that set properties affecting visual size of a component,
like font-size
, line-height
, and padding
should have big counterparts.
Big variables always have the -big
suffix appended to the end:
$button-padding: dynamic(1rem);
$button-padding-big: dynamic(1.2rem);
SASS rules target big mode using the .x-big
selector:
.#{$prefix}button {
padding: $padding;
.#{$prefix}big & {
padding: $padding-big;
}
}
##Component UIs
Every component should have a UI mixin for generating multiple visual
renditions of that component. The mixin should be named [xtype]-ui
.
For example button-ui
or panel-ui
.
UI mixins should have a parameter for each of the component's global variables,
and the parameter names should be the same as the global variable names with
the exception that the mixin parameters should not contain the xtype in their names.
For example, a global variable named $button-border-radius
, would correspond
to a parameter of the button-ui
mixin named $border-radius
.
The parameters to the UI mixin should all default to null, and should not produce any output if unspecified by the caller. This means that when the mixin is invoked it should produce a set of styling that represents a delta from the default UI. This minimizes the number of css rules required to create new UIs since the mixin automatically eliminates any null values from the output.
The styling for the default UI should be applied using CSS class names that
do not contain a UI name, for example x-button
, not x-button-default
.
This is the key to minimizing the number of rules required to create additional
UIs, since all buttons will have the x-button
class in addition to one
or more optional x-button-[ui]
classes. It allows the default UI to
serve as a base set of styles for all other UIs.
A typical UI mixin should look something like this:
@mixin button-ui(
$ui: null
// $xtype is the only variable that does not default to null
// since it is only used in selector names and does not affect
// the output unless other non-null values are passed. It is
// typically only passed by mixins of subclasses (see section on
// "Derived UIs" below)
$xtype: button,
$background-color: null,
$border-radius: null
) {
// returns '' if $ui is null, otherwise prefixes $ui with "-"
// To generate default UI we will not pass the $ui parameter
$ui-suffix: ui-suffix($ui);
.#{$prefix}#{$xtype}#{$ui-suffix} {
// Fashion compiler removes null values from output
// If all values are null, the entire rule is eliminated.
// This means there is usually no need to check == null
background-color: $background-color;
border-radius: $border-radius;
}
}
Since all UI mixin parameters default to null, the default UI invocation
must explicitly pass all the global variables for the component. This
generates the set of base styling rules for the component that all other
UIs build upon. Note that this invocation does not pass $ui so that the
base styles are applied to the base x-button
class, not a UI-specific one:
@include button-ui(
$background-color: $button-background-color,
$border-radius: $button-border-radius
);
To generate additional UIs, invoke the mixin again, passing only the parameters that are different from the default UI. For example, the following mixin call generates a UI named "action" that builds upon the default UI by changing the background-color to red, but inherits all other properties from the default UI via the cascade. The output from this UI invocation is very minimal - it only contains the rule or rules needed to set the background-color, nothing else.
@include button-ui(
$ui: 'action',
$background-color: red
);
Derived UIs
Every subclass of a Component that has a UI mixin should also have its
own UI mixin, along with a complete set of global variables for configuring
that mixin. The sublcass mixin should inoke the superclass mixin passing
its own xtype as the $xtype
parameter before adding any rules of its own.
Any subclass variables that are not intended to have different default
values from the superclass variables should default to null
.
This ensures that they will inherit the proper values via the parent CSS
class rather than redefining a redundant value. NOTE: you must define a
classCls
equal to x-[xtype]
on both Components in order for this pattern
to work (See section below on CSS class names). An example of this pattern
is the grid pagingtoolbar
component which extends toolbar
:
// theme-neptune/sass/var/grid/plugin/PagingToolbar.scss
$pagingtoolbar-background-color: dynamic(null);
@mixin pagingtoolbar-ui(
$ui: null,
$xtype: pagingtoolbar,
$background-color: null,
$prev-icon: null
) {
$ui-suffix: ui-suffix($ui);
// Call base toolbar mixin.
// Only produces output for non-null parameters
@include toolbar-ui(
$ui: $ui,
$xtype: $xtype,
$background-color: $background-color
);
// paging toolbar specific styles
.#{$prefix}#{$xtype}#{$ui-suffix} {
.#{$prefix}icon-prev {
@include icon($prev-icon);
}
}
}
// theme-neptune/sass/src/grid/plugin/PagingToolbar.scss
@include pagingtoolbar-ui(
$background-color: $pagingtoolbar-background-color;
);
##Configuring Theme UIs in Derived Themes
In the classic toolkit additional UIs provided with a theme typically were accompanied by a complete set of global variables for configuring that UI in derived themes. This resulted in massive amounts of duplication (variable count = total component vars * number of UIs). The modern toolkit takes a simpler approach - additional UIs provided by a theme are not configurable via global variables. Instead, these UIs are wrapped in a mixin of their own, which can be overridden by derived themes to change the parameters:
@mixin button-action-ui() {
@include button-ui(
$ui: 'action',
$background-color: red
);
}
Themes should provide a single variable for each additional UI that defaults to "true" but can be overridden to "false" in derived themes to completely disable generation of the UI. This variable should have the same name as the corresponding mixin:
@if $button-action-ui {
@include button-action-ui;
}
##Composable UIs
UIs should be composable where possible. For example if 2 separate button renditions are required, a red "action" button, and a rounded red "action" button, simply create an "action" UI and "round" UI:
// sass/var
$button-action-ui: dynamic($enable-default-uis);
$button-confirm-ui: dynamic($enable-default-uis);
@mixin button-action-ui() {
@include button-ui(
$ui: 'action',
$background-color: red
);
}
@mixin button-round-ui() {
@include button-ui(
$ui: 'round',
$border-radius: 10000px
);
}
// sass/src
@if $button-action-ui {
@include button-action-ui
}
@if $button-round-ui {
@include button-round-ui
}
To compose UIs simply use any number of UI names, space separated, in your component config:
ui: 'action round'
Note, that if multiple UIs set the same properties, the winner is the last one in the cascade, i.e. the one whose mixin was invoked last. To avoid confusion, composable UIs should typically strive to limit their area of concern to separate aspects of styling (colors, sizing, border-radius, etc), so that there is little ambiguity when combining them.
Using composable UIs ensures that the generated CSS code remains very DRY, by avoiding unnecessary duplication of CSS rules. In the example above, we avoid duplication of the background-color rules for every UI that may optionally need roundness, since any UI can be combined with the "round" UI to add roundness.
##Size Optimizing Mixins
Some css properties (like border-width, border-color, and border-style) can sometimes be collapsed in to a single property (border). Themes should use mixins that intelligently collapse these properties if possible to optimize CSS file size.
Pass border variables to the border
mixin to collapse them into a single
border declaration if possible. If any values are null
or if any values
are a list then it will output separate border-width
, border-color
, and
border-style
properties, otherwise it will output a single border
property.
.x-foo {
@include border($border-width, $border-style, $border-color);
}
Likewise the font
mixin should always be used for generating a font
declaration from variables, since it also collapses properties into a
single font
property if possible
.x-foo {
@include font($font-weight, $font-size, $line-height, $font-family);
}
Margin and padding should always be included using the margin
and padding
mixins. This allows themes to specify margin and padding variables as
a list that may include null
values. A null
value means "do not set
the value", and in many cases is preferred over 0
. For example:
// sass
$foo-padding: dynamic(1em, null);
.x-foo {
@include padding($foo-padding);
}
// css output:
.x-foo {
padding-top: 1em;
padding-bottom: 1em;
}
CSS Class Names
It is important to maintain consistency in naming of CSS classes since they they play a major role in adding semantics and structure to the DOM elements that are generated by Components in the framework. This consistency of naming serves two purposes:
- It improves the readability and maintainability of SASS code, and reduces the chance for error.
- For users who do not use the SASS API, it provides a clear and understandable dom structure for styling.
Main Element and UI CSS Classes
Each Component should have a class name of x-[xtype]
on its main element. For example,
a Text Field component should have a CSS class name of x-textfield
. There are two
possible ways for Components to set this main CSS class. They can either set
classCls
or baseCls
on the body of their class definition (classCls
is preferred).
Setting either of these will add the CSS class to the main element and use it as the prefix
for UI-specific class names that are also added to the main element. classCls
and baseCls
only
differ in their inheritability. classCls
is inherited by subclasses and is additional to the
classCls
of those subclasses, whereas baseCls
is not. Additionally, when using
classCls
a UI-specific CSS class name will be added for each and every classCls
in the
class hierarchy. For example, a Ext.field.Password
component with a ui
of foo
would
have the following UI classes:
x-passwordfield-foo
x-textfield-foo
x-field-foo
Using this pattern ensures that styling is correctly inherited through the class hierarchy
and allows Components to only provide styling for the functionality that they add. For
odd edge cases where inheriting styling is not desired Components may set classClsRoot:true
to prevent inheritance of classCls
from ancestors, but this should usually be avoided.
Reference Element CSS Classes
Reference elements should follow the pattern x-[referencePrefix]-el
. For example
the bodyElement
reference element of a form field should have the CSS class
x-body-el
(for consistency element references should always have the Element
suffix on the JavaScript side). The -el
suffix on the CSS class name helps
to differentiate the reference element from a potential Component with an
xtype of the same name.
CSS Classes for Component Configuration and State
CSS class names that reflect Component configuration should follow the
pattern x-[configName]-[configValue]
, and should always be placed on the Component's
main element. For example a form field with a labelAlign: 'left'
config would result in
a CSS class name of x-label-align-left
being added to the main element.
CSS class names for boolean configs should generally follow one of two patterns
Truthiness causes a new class to be added - for example a checkbox with a
checked
config would have ax-checked
CSS class when the value is true, but would not have the class when false.Falsiness causes a new class to be added. This is sometimes useful when the default value is true, and the component needs needs additional styling only in the falsy state. For example, the List component has a
x-no-row-lines
CSS class whenrowLines
is configured asfalse
.
Likewise class names that reflect Component state should follow the pattern
x-[state]
, and should always be placed on the Component's main element .
For example, a button that is in pressed state would have the class x-pressed
on
its main element.
Setting Component state and configuration CSS classes on the main element, rather than on a reference element allows the state or configuration to be scoped to the Component, even if these classes only affect the styling of a child element or elements. This also results in a more stable dom structure as these class names do not change location even if the internal dom structure is modified.
CSS Selectors
Since reference, config, and state CSS classes do not contain xtype info in their name,
they must be used in combination with the Component's classCls
or baseCls
to avoid
colliding with other Components that may have the same config or state class name. For
example to style the pressed state of a button's main element, one would use the following
selector:
.x-button.x-pressed
UI mixins should use UI-specific CSS class names in combination with reference, config,
and state CSS classes. For example if the ui
of a button is 'foo'
, one would style
the pressed state as follows:
.x-button-foo.x-pressed
Child vs Descendant Selectors
When styling a component's inner elements descendant selectors such as .x-foo .x-bar
should be preferred over direct child selectors like .x-foo > .x-bar
. This allows for
much more flexibility in the markup and allows it to tolerate more change, such as the
insertion of a wrapping element in between x-foo
and x-bar
without potentially breaking
the styling. The only exception to this rule is when there is the potential for nesting.
For example, a panel might use a selector such as .x-panel > .x-body-el
in order to only
style its own body element, and not the body elements of other panels nested within it.
In some cases when there may be a varying number of dom elements in between
the container element and it's child it may be necessary to add UI-specific
class names to the child element, but this should be treated as the exception,
not the rule. An example of this is Ext.Container
. It adds a UI-specific
class for each classCls
to its bodyElement
because there can be a varying
number of DOM ancestors in between the bodyElement
and the element
depending on whether or not the container has docked items.