User:JDrewniak (WMF)/notes/Understanding mw-ui-icon

mw-ui-icon

@import 'mediawiki.mixins';
@import 'mediawiki.ui/variables';

// Mixins
.mixin-mw-ui-icon-bgimage( @iconSvg, @iconPng ) {
	&.mw-ui-icon {
		&:before {
			.background-image-svg( @iconSvg, @iconPng );
		}
	}
}

// Icons
//
// To use icons you must be using a browser that supports pseudo elements.
// This includes support for IE 8.
// https://caniuse.com/#feat=css-gencontent
//
// For elements that are intended to have both an icon and text, browsers that
// do not support pseudo-selectors will degrade to text-only.
//
// However, icon-only elements do not yet degrade to text-only elements in these
// browsers.
//
// Styleguide 6.

.mw-ui-icon {
	position: relative;
	line-height: @iconSize;
	min-height: @iconSize;
	min-width: @iconSize;

	// If an inline element has been marked as a mw-ui-icon element it must be inline-block
	span& {
		display: inline-block;
	}

	// Standalone icons
	//
	// Markup:
	// <div class="mw-ui-icon mw-ui-icon-element mw-ui-icon-ok">OK</div><br>
	// <div class="mw-ui-icon mw-ui-icon-element mw-ui-icon-ok mw-ui-button mw-ui-progressive">OK</div><br>
	// <button class="mw-ui-icon mw-ui-icon-ok mw-ui-icon-element mw-ui-button mw-ui-quiet" title="">Close</button>
	//
	// Styleguide 6.1.1.
	&.mw-ui-icon-element {
		@marginIcon: 2 * @iconGutterWidth;
		@width: @iconSize + @marginIcon;
		@sizeIconLarge: ( @iconSize * 1.75) + @marginIcon;
		text-indent: -999px;
		overflow: hidden;
		width: @width;
		min-width: @width;
		max-width: @width;

		&:before {
			left: 0;
			right: 0;
			position: absolute;
			margin: 0 @iconGutterWidth;
		}

		&.mw-ui-icon-large {
			width: @sizeIconLarge;
			min-width: @sizeIconLarge;
			max-width: @sizeIconLarge;
			line-height: @sizeIconLarge;
			min-height: @sizeIconLarge;

			&:before {
				min-height: @sizeIconLarge;
			}
		}
	}

	&.mw-ui-icon-before:before,
	&.mw-ui-icon-element:before {
		background-position: 50% 50%;
		background-repeat: no-repeat;
		background-size: 100% auto;
		float: left;
		display: block;
		min-height: @iconSize;
		content: '';
	}

	// Icons with text
	//
	// Markup:
	// <div class="mw-ui-icon mw-ui-icon-before mw-ui-icon-ok">OK</div>
	// <div class="mw-ui-icon mw-ui-icon-before mw-ui-icon-ok mw-ui-progressive mw-ui-button">OK</div>
	//
	// Styleguide 6.1.2
	&.mw-ui-icon-before {
		&:before {
			position: relative;
			width: @iconSize;
			margin-right: @iconGutterWidth;
		}
	}

	// Icons small for elements like indicators
	//
	// Markup:
	// <div class="mw-ui-icon mw-ui-icon-small mw-ui-icon-help"></div>
	//
	// Styleguide 6.1.3
	&.mw-ui-icon-small:before {
		background-size: 66.67% auto; // 66.67% of 24px equals 16px
	}
}

Expected usage

edit

Standalone icon:

<div class="mw-ui-icon mw-ui-icon-element mw-ui-icon-ok">OK</div>

Icon with text:

<div class="mw-ui-icon mw-ui-icon-before mw-ui-icon-ok">OK</div>
NOTE: "-element" and "-before" are poor names. They do not describe the behaviour of these classes, but rather their implementation details. Modifier names like “-standalone” and “-with-text” would better describe what these classes do.

In programming terms, mw-ui-icon can be thought of as exposing the following interface:

a base class, a modifier class, and an instance property.

The base class is mw-ui-icon, the modifier class is one of [ mw-ui-icon-before, mw-ui-icon-element ] and the property is one of many icon image urls mw-ui-icon-{icon name}.

By themselves, these classes produce nothing. The base class does not have a “default” state and requires a modifier class to have any effect.

i.e. <div class="mw-ui-icon mw-ui-icon-ok">OK</div> doesn’t work.

The line-by-line

edit

Now we go through the code above line-by-line and see what we find.

Line #27 .mw-ui-icon This base class just sizes & positions the box (min-height/min-width/line-height/relative).

.mw-ui-icon {
	position: relative;
	line-height: @iconSize;
	min-height: @iconSize;
	min-width: @iconSize;


Line #34 - span& This guards against a single inline element, <span>

	// If an inline element has been marked as a mw-ui-icon element it must be inline-block
	span& {
		display: inline-block;
	}
NOTE: shouldn’t we guard against all possible inline elements and set display: inline-block; as a rule in the base class? What if someone uses an "i" element?


line #46 - &.mw-ui-icon-element is a class to create “stand-alone” icons. It hides text, overrides the base width, and creates an absolutely positioned :before pseudo-element.

	&.mw-ui-icon-element {
		@marginIcon: 2 * @iconGutterWidth;
		@width: @iconSize + @marginIcon;
		@sizeIconLarge: ( @iconSize * 1.75) + @marginIcon;
		text-indent: -999px;
		overflow: hidden;
		width: @width;
		min-width: @width;
		max-width: @width;
NOTE: This sets min-width/max-width and width. Like, it really wants that width to change. Why just the width? Shouldn’t icons always be a square?


Line #63 oh! An additional modifier class. &.mw-ui-icon-large . This resized the icon again (min/max/width and min/line/height).

		&.mw-ui-icon-large {
			width: @sizeIconLarge;
			min-width: @sizeIconLarge;
			max-width: @sizeIconLarge;
			line-height: @sizeIconLarge;
			min-height: @sizeIconLarge;

			&:before {
				min-height: @sizeIconLarge;
			}
		}

The expected usage is probably:

<div class="mw-ui-icon mw-ui-icon-element mw-ui-icon-large mw-ui-icon-ok">OK</div>
NOTE: Seeing as how this class is coupled with the .mw-ui-icon-element class, the name should probably be .mw-ui-icon-element-large .


Line #76 - common rules for mw-ui-icon-before and .mw-ui-icon-element. These rules style the :before pseudo-element which is the container for the actual icon image (placed as a background-image). The min-height of this pseudo-element is declared but the width is not. The width is declared in the modifier classes instead.

	&.mw-ui-icon-before:before,
	&.mw-ui-icon-element:before {
		background-position: 50% 50%;
		background-repeat: no-repeat;
		background-size: 100% auto;
		float: left;
		display: block;
		min-height: @iconSize;
		content: '';
	}
NOTE: Since these are shared rules, it seems like they should be placed into the base .mw-ui-icon class instead.


Line #94 - .mw-ui-icon-before is a modifier class that accommodates “icons with text”. Essentially providing a right-margin and relatively positioning the :before pseudo element (instead of absolutely positioning it like .mw-ui-icon-element so that the pseudo-element is positioned beside the elements text content.

	&.mw-ui-icon-before {
		&:before {
			position: relative;
			width: @iconSize;
			margin-right: @iconGutterWidth;
		}
	}
IMO: This combination of icon+text shouldn't exist. The icon element should be responsible for just the icon. I can't see a reason why we can't place the text beside the icon element instead of inside it, like
<span class="icon"></span> text beside element
.

Visual Representation

edit

https://codepen.io/j4n/pen/RwbKMwm