PDA

View Full Version : A slicker SASS mixin for creating coloured buttons



Nigel
21 Dec 2012, 12:32 PM
Now that our SaaS application is fully live (desktop UI using ExtJS 3.4), it was time to start work in earnest on updating the UI codebase to ExtJS 4.x.
One of the subjects that we were most excited to tackle was the move from hand-crafted CSS and images to SASS/Compass-based styling.

An early experiment involved creating coloured buttons - something which, in v3.x and earlier, involved cranking up a graphics file editor and making copies of the standard Sencha-supplied button sprite images - all a bit clunky, especially if you needed a few different ones.

So I was excited to try making a simple green button. It should be easy - just specify 'ui: green' on the button config, and use the mixin provided - @mixin extjs-button-ui in /resources/themes/stylesheets/ext4/default/widgets

Having already put my corporate colours in variables at the top of my .scss file,


...
$xampe-green: #6eb43f;
...
I was expecting the following to do the job for me:


@include extjs-button-ui(
'green',

$background-color: $xampe-green,
$color: white
);

Not quite - the first 'gotcha' was that you need to be slightly more specific about the button ui - each button is created as a combination of its 'ui' setting AND its size - so 'green' above needed to change to 'green-small'.
Next, I discovered that this produced a result (not exactly desired, by a long stretch) for 'ordinary' small green buttons, but I also needed a green button on our application's main toolbar - so I had to repeat the @include call again using 'green-toolbar-small' as the 'ui'.

But the real problem was that I was assuming that the mixin would make lots of assumptions for me - but on digging in to the code, I realised that it makes practically none. It doesn't set defaults from the 'standard' button settings, and it expects almost all variables to be set specifically. Not only did this include things like colours, font settings etc, but even really basic stuff like the icon-width. This tripped me up when, after getting almost everything else sorted out, I noticed 'Hey, where did my icons go on my nice new coloured buttons?' - I had not explicitly set the $icon-size variable in the mixin call, so it defaulted to 'null' rather than the '16px' default for small buttons (set, along with pretty much everything else you'd ever need, in /resources/themes/stylesheets/ext4/default/variables

After plenty of digging around in the provided scss files (well worth spending time in to understand what's really going on) and generated css (which very helpfully adds comments referring back to the original scss files) and searching this forum and elsewhere, I hadn't come up with much useful code or better ideas, so I decided to write about my experiences here in case it helps anyone else on the same journey.

The Result
I eventually got my green button working more or less how I wanted it, with the following:


@include extjs-button-ui(
'green-small',

$background-color: $xampe-button-green,
$background-color-over: adjust-color($xampe-button-green, $hue: 0deg, $saturation: 0, $lightness: -3.725%),
$background-color-focus: adjust-color($xampe-button-green, $hue: 0deg, $saturation: -9.556%, $lightness: 12.745%),
$background-color-pressed: adjust-color($xampe-button-green, $hue: -0.725deg, $saturation: -9.556%, $lightness: 12.745%),
$background-color-disabled: adjust-color($xampe-button-green, $hue: 0deg, $saturation: -9.556%, $lightness: 12.745%),

$border-radius: $button-small-border-radius,
$border-width: $button-small-border-width,
$border-color: darken($xampe-button-green, 10),

$color: white,
$color-over: white,
$color-disabled: white, // Necessary to avoid bug in mixin that requires color-disabled != null
$font-family: $button-small-font-family,
$font-family-over: $button-small-font-family,
$font-family-focus: $button-small-font-family,
$font-family-pressed: $button-small-font-family,
$font-family-disabled: $button-small-font-family,
$font-size: $button-small-font-size,
$font-size-over: $button-small-font-size,
$font-size-focus: $button-small-font-size,
$font-size-pressed: $button-small-font-size,
$font-size-disabled: $button-small-font-size,

$background-gradient: $button-default-background-gradient,
$background-gradient-over: $button-default-background-gradient-over,
$background-gradient-focus: $button-default-background-gradient-focus,
$background-gradient-pressed: $button-default-background-gradient-pressed,
$background-gradient-disabled: $button-default-background-gradient-disabled,

$icon-size: 16px
);


and the whole thing repeated with 'green-toolbar-small' as the first variable ($ui). Whilst this worked, it offended my sensibilities on many levels - I was having to explicitly state things that I should (IMHO) have been able to ignore, and I had to repeat a lot of code. As soon as I'd added a red button as well, I resolved that the only way to make this slicker was to write my own mixin - or at least, to take the existing mixin and hack it around.

This I did, and to my great satisfaction, got it all working beautifully. I now have the following to create red, green and blue buttons:



@include xampe-button-ui(
'green-small',

$background-color: $xampe-button-green,
$color: white
);

@include xampe-button-ui(
'green-toolbar-small',

$background-color: $xampe-button-green,
$color: white
);

@include xampe-button-ui(
'red-small',

$background-color: $xampe-button-red,
$color: white
);

@include xampe-button-ui(
'blue-small',

$background-color: lighten($xampe-blue, 20),
$color: white
);


These use the following revised mixin - it keeps most of the original, but removes a bunch of stuff I knew I'd never need, like old browser support, ability to change font name and weight by button state, etc (comments throughout the code below should help).


/**
* Creates a visual theme of an Ext.Button
* @member Ext.button.Button
*
* Requires at minimum just the ui, background-color and color. Everything else is derived from these,
* but can be overridden by explicitly setting additional variables
*
* This is modified from extjs-button-ui mixin, with a few changes in assumptions:
* - buttons created from this mixin are always small, so we use the default sizes for small buttons throughout
* - we never change font size or weight based on button state, so just use one variable for these
* - we're only supporting modern browsers so removed some of the stuff needed to support old IE etc
*/
@mixin xampe-button-ui(
$ui,
$background-color,
$color,

$font-size: $button-small-font-size,

$border-radius: $button-small-border-radius,
$border-width: $button-small-border-width,

$border-color: null,
$border-color-over: null,
$border-color-focus: null,
$border-color-pressed: null,
$border-color-disabled: null,

$padding: $button-small-padding,
$text-padding: $button-small-text-padding,

$background-color-over: null,
$background-color-focus: null,
$background-color-pressed: null,
$background-color-disabled: null,

$background-gradient: $button-default-background-gradient,
$background-gradient-over: $button-default-background-gradient-over,
$background-gradient-focus: $button-default-background-gradient-focus,
$background-gradient-pressed: $button-default-background-gradient-pressed,
$background-gradient-disabled: $button-default-background-gradient-disabled,

$color-over: null,
$color-focus: null,
$color-pressed: null,
$color-disabled: null,

$font-size: null,

$font-weight: null,

$icon-size: $button-small-icon-size
) {
.#{$prefix}btn-#{$ui} {
@if $border-color != null {
border-color: $border-color;
} @else {
/**
* If we haven't explicitly set a border-color, then generate it from the background-color
*/
border-color: darken($background-color, 10);
}
}

@include x-frame('btn', $ui, $border-radius, $border-width, $padding, $background-color, $background-gradient, true);

.#{$prefix}btn-#{$ui} .#{$prefix}btn-inner {
@if $font-size != null {
font-size: $font-size;
}
@if $font-weight != null {
font-weight: $font-weight;
}
color: $color;
background-repeat: no-repeat;
padding: 0 $text-padding;
}

.#{$prefix}btn-#{$ui}-icon,
.#{$prefix}btn-#{$ui}-noicon {
button,
a,
.#{$prefix}btn-inner {
height: $icon-size;
line-height: $icon-size;
}
}

//icons
.#{$prefix}btn-#{$ui}-icon {
button,
a {
padding: 0;
}

.#{$prefix}btn-inner {
/* even though there is no text we set a width and padding as buttons shrink-wrap around this element */
width: $icon-size;
padding: 0;
}

.#{$prefix}btn-icon {
width: $icon-size;
height: $icon-size;
top: 0;
left: 0;
bottom: 0;
right: 0;
}
}

.#{$prefix}btn-#{$ui}-icon-text-left {
button,
a {
height: $icon-size;
}
.#{$prefix}btn-inner {
height: $icon-size;
line-height: $icon-size;
padding-left: $icon-size + 4px;
}

.#{$prefix}btn-icon {
width: $icon-size;
height: auto;
top: 0;
left: 0;
bottom: 0;
right: auto;
}
}

.#{$prefix}btn-#{$ui}-icon-text-right {
button,
a {
height: $icon-size;
}
.#{$prefix}btn-inner {
height: $icon-size;
line-height: $icon-size;
padding-right: $icon-size + 4px !important;
}

.#{$prefix}btn-icon {
width: $icon-size;
height: auto;
top: 0;
left: auto;
bottom: 0;
right: 0;
}
}

.#{$prefix}btn-#{$ui}-icon-text-top {
.#{$prefix}btn-inner {
padding-top: $icon-size + 4px;
}

.#{$prefix}btn-icon {
width: auto;
height: $icon-size;
top: 0;
left: 0;
bottom: auto;
right: 0;

}
}

.#{$prefix}btn-#{$ui}-icon-text-bottom {
.#{$prefix}btn-inner {
padding-bottom: $icon-size + 4px;
}

.#{$prefix}btn-icon {
width: auto;
height: $icon-size;
top: auto;
left: 0;
bottom: 0;
right: 0;
}
}

.#{$prefix}btn-#{$ui}-over {
@if $border-color-over != null {
border-color: $border-color-over;
}
@if $background-color-over != null {
@include background-gradient($background-color-over, $background-gradient-over);
} @else {
/**
* If we don't specify a specific background-color-over, then we can create one
* from our background-color. We apply adjust-color using the same ratios as in
* the original resources/themes/stylesheets/ext4/default/variables/_button.scss
*/
@include background-gradient(adjust-color($background-color,
$hue: -6.667deg, $saturation: 5%, $lightness: 11%),
$background-gradient-over);
}

.#{$prefix}btn-inner {
@if $color-over != $color {
color: $color-over;
}
}
}

.#{$prefix}btn-#{$ui}-focus {
@if $border-color-focus != $border-color {
border-color: $border-color-focus;
}

@if $background-color-focus != null {
@include background-gradient($background-color-focus, $background-gradient-focus);
} @else {
/**
* If we don't specify a specific background-color-focus, then we can create one
* from our background-color. We apply adjust-color using the same ratios as in
* the original resources/themes/stylesheets/ext4/default/variables/_button.scss
*/
@include background-gradient(adjust-color($background-color,
$hue: -6.667deg, $saturation: 5%, $lightness: 11%),
$background-gradient-focus);
}

.#{$prefix}btn-inner {
@if $color-focus != $color {
color: $color-focus;
}
}
}

.#{$prefix}btn-#{$ui}-menu-active,
.#{$prefix}btn-#{$ui}-pressed {
@if $border-color-pressed != $border-color {
border-color: $border-color-pressed;
}

@if $background-color-pressed != null {
@include background-gradient($background-color-pressed, $background-gradient-pressed);
} @else {
/**
* If we don't specify a specific background-color-pressed, then we can create one
* from our background-color. Adjust ratios to taste!
*/
@include background-gradient(adjust-color($background-color,
$hue: -0.725deg, $saturation: -10%, $lightness: -4%),
$background-gradient-pressed);
}

.#{$prefix}btn-inner {
@if $color-pressed != $color {
color: $color-pressed;
}
}
}

.#{$prefix}btn-#{$ui}-disabled {
@if $border-color-disabled != $border-color {
border-color: $border-color-disabled;
} @else {
border-color: lighten($background-color, 10);
}
@if $background-color-disabled != null {
@include background-gradient($background-color-disabled, $background-gradient-disabled);
} @else {
/**
* If we don't specify a specific background-color-disabled, then we can create one
* from our background-color. Adjust ratios to taste!
*/
@include background-gradient(adjust-color($background-color,
$hue: 0deg, $saturation: -6%, $lightness: 12%),
$background-gradient-disabled);
}

.#{$prefix}btn-inner {
@if $color-disabled != $color {
color: $color !important;
}
}
}

.#{$prefix}ie .#{$prefix}btn-#{$ui}-disabled {
.#{$prefix}btn-inner {
@if ($color-disabled != $color and $color-disabled != null) {
color: darken($color-disabled, 20) !important;
}
}
}
};

This won't necessarily meet all your specific needs as-is (note the comments on what has been taken out, mainly to keep the code manageable and to reduce unnecessary css in the output file), but I hope that this helps your own journey in understanding SASS. Please comment back with any ideas for improvements etc.

Misiu
11 Sep 2013, 6:04 AM
This is a bit old topic, but it helped me a lot in creating my custom theme.
My old Flex based application has style that I really like, right now I'm in the middle of migrating it from Flex to Ext JS.
Code is one part, but I wanted to style it to look almost the same.
Your post was just the thing I was looking for :)