# Работа с SASS компонентами

В данном разделе будут продемонстрированы основные принципы работы с SASS компонентами. Для ознакомления с компонентами посетите данный раздел.

# Создание компонента

Создание компонента осуществляется непосредственно через CLI меню scss плагина с помощью команды create <cid>:

ungic> scss
ungic scss> create myComponent

# Удаление компонента

Для корректного удаления компонента, рекомендуется использовать remove <cid> команду в CLI меню scss плагина.

TIP

Удаление с помощью команды позволяет безопасно удалить компонент! В случае, если в проекте имеются компоненты которые зависят от данного компонента, то удаление не будет выполнено!

# Принцип написания CSS правил

Все CSS правила стилей следует включать в render.scss файл.

Если компонент имеет мало стилей, все правила можно поместить в render.scss файл

// components/app/render.scss
@use ".core" as this;

.btn {
	...
}

Когда стилей больше, следует разделить их на части любым удобным для вас способом, а затем включить их в render.scss файл используя один из методов dart sass, таких как @use (opens new window), @forward (opens new window), meta.load-css (opens new window)

// components/app/render.scss
@use ".core" as this;

@forward "controls/btn.scss";
@forward "controls/panel.scss";

# Рекомендуемый подход к написанию стилей

При написании стилей компонента рекомендуется:

  • Использовать имя компонента в виде корневого имени селектора (например класс с именем компонент .app)
  • Дочерние элементы должны наследовать имя компонента (например используя разделитель .app-btn)

Преимущество такого подхода заключается в том, что стили такого компонента будут находиться в изолированном виде и не будут пересекаться с другими стилями компонентов, кроме того, данный способ позволяет разделять компонент на отдельные UI части и избавиться от иерархической зависимости!

Для того, чтобы помочь придерживаться вышеперечисленным рекомендациям, были созданы различные инструменты, разберем их:

# Mixin this.component

Миксин ядра компонента, создает обертку в виде класса с именем компонента.

Использование:

// components/app/render.scss
@use ".core" as this;

@include this.component {
	display: block; // .app {display:block}
	&-method {
		// .app-method {...}
	}
	&-element {
		// .app-element {...}
	}
	&.method {
		// .app.method {...}
	}
}

# Инструменты для работы с селектором

Для того, чтобы всячески отталкиваться от класса компонента, существуют следующие методы:

// components/app/render.scss
@use ".core" as *;

@include component {
	display:block;

	&-body {
		display: block
	}

	@include this(-header) {
		@include this {
			&-btn {
				display: inline-block
			}
		}
	}

	#{this(-footer)} {
		display: block
	}

	&+#{cid(true)} {
		display: flex
	}

}

Результат

	.app {
		display:block;
	}
	.app-body {
		display: block;
	}
	.app .app-header {
		.app-btn {
			display:inline-block;
		}
	}
	.app .app-footer {
		display: block;
	}
	.app + .app {
		display: flex;
	}

Инструменты для более сложных задач

Данные методы производят сложение переданного суффикса с классом компонента и добавляют полученный результат в определенное место цепочки селектора.

Без параметров ведут себя однотипно, класс текущего компонента добавляется к текущему селектору:

// components/app/render.scss
@use ".core" as *;

@include component {
	&-header {
		@include wrap {
			@debug &; // .app-header.app {...}
			&-test {
				@debug &; // .app-header.app-test {...}
			}
		}
	}
}

Метод wrap производит сложение переданного суффикса с классом компонента и добавляют полученный результат в самое начало селектора

// components/app/render.scss
@use ".core" as *;

@include component {
	&-header {
		@include wrap('-opened') {
			&debug &; // .app-opened .app-header
		}
		@include wrap('.opened') {
			&debug &; // .opened .app-header
		}
		@include wrap('html', false) {
			&debug &; // html .app-header
		}
	}
}

TIP

Обратите внимание! С помощью таких методов как wrap, prev и before невозможно обогнать префиксы которые назначаются фреймворком! Системные префиксы, которые отвечают за direction или темизацию всегда будут первыми!

// components/app/render.scss
@use ".core" as *;
@use "ungic.theme" as *;

@include component {
	&-label {
		color: color(text-color);
		@include wrap('-danger') {
			color: color(danger);
			margin-left: 10px;
		}
	}
}

Результат:

.app-label {
	color: #444;
}
.app-danger .app-label {
	color: red;
}
.un-inverse .app-danger .app-label {
	color: red;
}
[dir="ltr"] .app-danger .app-label {
	margin-left: 10px;
}
[dir="rtl"] .app-danger .app-label {
	margin-right: 10px;
}

Метод and изменяет корневой класс селектора:

// components/app/render.scss
@use ".core" as *;

@include component {
	@include this {
		&-icon {
			display: none; // .app .app-icon {display:none}
			@include and('.has-icon, .big-icon') {
				// .app.has-icon .app-icon,
				// .app.big-icon .app-icon {display:block}
				display: block;
			}
		}
	}
	&-header {
		@include and('__this') {
			@debug &; // .test.test-header
		}
		@include and('-section') {
			@debug &; // .test-section-header
		}
	}
}

prev и before методы возвращаются к предыдущему селектору:

  • prev - изменяет предыдущий элемент селектора
  • before - добавляет часть селектора перед текущим элементом
// components/app/render.scss
@use ".core" as *;
@use "ungic.theme" as *;

@include component {
	&-header {
		// .btn - текущий элемент
		.btn {
			@include prev('.lg, -lg') {
				@debug &; //  .app-header.lg .btn, .app-header-lg .btn
			}
			@include before('.lg, __this-lg') {
				@debug &; // .app-header .lg .btn, .app-header .app-lg .btn
			}
		}
	}
}

С помощью nest метода, можно создать правила для сестринских элементов компонента, данный метод добавляет селектор сразу после класса компонента.

// components/app/render.scss
@use ".core" as *;

@include component {
	@include component {
		@include nest('+__this __this') {
			&-btn {
				@debug &; // .test + .test .test-btn
			}
		}
		&-btn {
			@include nest('+__this __this') {
				@debug &; // .test + .test .test-btn
			}
		}
	}
}

Поддержка нескольких селекторов, все методы поддерживают данную возможность, достаточно передать несколько частей селектора через запятую в виде строки:

// components/app/render.scss
@use ".core" as *;

@include component {
	&-header {
		@include wrap('-test, -best') {
			@debug &; // .app-test .app-header, .app-best .app-header
		}
		@include wrap('-test, .test') {
			@debug &; //  .app-test .app-header, .app.test .app-header
		}
	}
}

Строка суффикса может содержать подстановочные теги ___this или __this, которые будут заменены на имя или класс текущего компонента

  • __this - заменяется на класс компонента
  • ___this - заменяется на имя компонента
// components/app/render.scss
@use ".core" as *;

@include component {
	&-header {
		@include wrap('__this-opened') {
			@debug &; // .app-opened .app-header
		}
		@include wrap('div__this') {
			@debug &; // div.app .app-header
		}
		@include wrap('__this-open__this __this-section') {
			@debug &; // .app-open.app .app-section .app-header
		}
	}
}

# Разделение на части и рендеринг по условию

Создадим демонстрационный компонент - библиотеку. Наш компонент будет соответствовать следующим требованиям:

  1. Рендер по умолчанию всех UI стилей
  2. Возможность отключения рендера всех UI стилей
  3. Возможность использования определенных UI стилей в других SASS компонентах

Создадим наш вымышленный SASS компонент с именем lib:

// components/lib/config.scss

// Назначим условие рендера по умолчанию
// в конфигурации компонента
$render: true;

Разделим все UI стили на части и включим их в render.scss по условию, если $render параметр из config.scss файла, является правдой:

// components/lib/render.scss
@use ".core" as this;

// Проверяем условие
@if this.config(render) {
	@include this.component {
		// Включаем все UI компоненты
		@include meta.load-css('./includes/popups.scss');
		@include meta.load-css('./includes/btns.scss');
		@include meta.load-css('./includes/cards.scss');
		...
	}
}

Каждый включаемый нами файл содержит стили обернутые с помощью utils.merge миксина, данный подход позволяет включать стили непосредственно в сам контекст:

//components/lib/includes/btns.scss
@use "ungic.utils" as utils;
@use "ungic.theme" as *;

@include utils.merge {
	&-btn {
		display: inline-block;
		&.primary {
			background-color: color(primary);
			color: #FFF
		}
	}
}

После того, как lib компонент будет сгенерирован в CSS, его содержимое будет выглядеть следующим образом:

.lib-btn {
	display: inline-block;
}
.lib-btn.primary {
	background-color: green;
	color: #FFF
}
.lib-popup {
	...
}
.lib-card {
	...
}

Наш компонент генерирует все стили UI компонентов. Отключим данное поведение и будем использовать только те UI компоненты, которые нам необходимы!

Представим, что lib компонент не принадлежит нам, в таком случае, менять его файлы не рекомендуется! Поэтому изменим его конфиг. переменную $render, непосредственно в config-over.scss файле проекта:

// project/config-over.scss

/* --------------------------------------------------*/
//	Don't change this part
@use "sass:map";
$cid: null !default;
$config: () !default;
/* ------------------------------------------------ */

// Проверяем $cid
@if($cid == lib) {
	// Отключаем render
	$config: map.merge($config, (render: false));
}

После этого, lib компонент больше не будет генерировать CSS стили! Осталось включить необходимые нами UI стили в нашем app компоненте:

// components/app/render.scss
@use ".core" as this;

@include this.component {
	// С помощью meta.load-css sass метода
	// включаем необходимые UI компоненты lib компонента
	// в любом месте нашего app компонента

	@include meta.load-css('ungic.components.lib.includes.btns');
}

После того, как app компонент будет сгенерирован в CSS, его содержимое будет выглядеть следующим образом:

.app-btn {
	display: inline-block;
}
.app-btn.primary {
	background-color: green;
	color: #FFF
}

Вот и все! Это очень примитивный и простой способ разделения компонента на части, данный подход не является достаточно гибким, для более гибкого подхода следует использовать миксины компонента.

# Написание стилей SASS компонентов в HTML документах

В ungic framework имеется возможность писать SASS правила компонента непосредственно из HTML документа. Осуществляется это, с помощью style элемента с добавлением пользовательских атрибутов фреймворка:

  • атрибут sass указывает на идентификатор SASS компонента
  • атрибут slot идентификатор пути с помощью которого происходит включение данных правил в сам компонент.

Для примера, создадим несколько внутренних стилей документа и назначим им наши пользовательские атрибуты, связав их с нашим app sass компонентом:

<!DOCTYPE html>
<html lang="en">
<head>
	<meta charset="UTF-8">
	<meta name="viewport" content="width=device-width, initial-scale=1.0">
	<title>Document</title>
	<!-- Include generated styles of app component -->
	<un-pipe sass="app" main="app"></un-pipe>
</head>
<body>
	<div class="@">
		<header class="@-header">
			<h1>Title</h1>
			<style sass="app" slot="header">
				@use "ungic.utils" as *;
				@use "ungic.theme" as theme;

				@include merge {
					&-header {
						h1 {
							color: theme.color(primary, .-6);
						}
					}
				}
			</style>
		</header>
		<footer class="@-footer">
			<p>Lorem ipsum dolor sit.</p>
			<style sass="app" slot="footer">
				@use "ungic.component" as this;
				@use "ungic.utils" as *;
				@use "ungic.theme" as theme;

				@include merge {					
					background-color: theme.color(gray, .2);
					p {
						font-size: this.prop(font-size);
					}					
				}
			</style>
		</footer>
	</div>
</body>
</html>

После того, как HTML документ будет сохранен, все правила предназначенные для SASS компонентов будут переданы SASS фреймворку и удалены из HTML документа.

TIP

Все style элементы, которые содержат sass атрибут будут удалены из скомпилированного HTML документа, именно поэтому их можно использовать в любом месте документа!

TIP

Если правила стилей не были затронуты перед предыдущим сохранением HTML документа, SASS фреймворк не будет производить их повторную компиляцию!

Для того, чтобы данные правила были сгенерированы SASS фреймворком, необходимо включить данные слоты в правила нашего app SASS компонента с помощью модуля ungic.slots.{slot_id}:

@use ".core" as this;
@use "sass:meta";

@include this.component {
	@include meta.load-css("ungic.slots.header");
	&-footer {
		@include meta.load-css("ungic.slots.footer");
	}
}

После данных действий, наши правила были успешно вставлены в наш SASS компонент и скомпилированы вместе с другими правилами. Для того, чтобы сгенерированные стили были применены к нашему документу, в режиме разработки используется un-pipe метод:

<un-pipe sass="app"></un-pipe>

После того, как мы выполним любые изменения в наших внутренних стилях и сохраним HTML документ, стили будут успешно сгенерированы и добавлены к нашему компоненту!