Styling Guidelines
Руководство по написанию стилей в проекте с использованием SCSS, Angular Material 3 и собственных миксинов.
Основные принципы
1. Вложенность
ВСЕГДА пишем стили вложенными, следуя структуре DOM.
// ✅ Правильно
.card {
padding: 16px;
.card-header {
display: flex;
.title {
font-size: 18px;
}
}
.card-content {
margin-top: 16px;
}
}
// ❌ Неправильно
.card {
padding: 16px;
}
.card-header {
display: flex;
}
.card .title {
font-size: 18px;
}2. Импорт abstractions
Каждый компонент ВСЕГДА начинается с импорта:
@use "../../../../../../assets/scss/abstractions/index" as *;Это даёт доступ ко всем миксинам проекта.
3. Миксин @include component()
ВСЕГДА оборачиваем стили компонента в @include component():
@use "../../../../../../assets/scss/abstractions/index" as *;
@include component() {
.my-component {
display: flex;
// остальные стили
}
}Это автоматически создаёт:
:host {
display: block;
}Design Tokens
Border Radius (Angular Material 3)
Используем системные переменные Material Design:
.card {
border-radius: var(--mat-sys-corner-medium); // 12px - стандарт
}
.badge {
border-radius: var(--mat-sys-corner-small); // 8px
}
.container {
border-radius: var(--mat-sys-corner-large); // 16px
}
.avatar {
border-radius: var(--mat-sys-corner-full); // 9999px - круглый
}Доступные значения:
--mat-sys-corner-none— 0px--mat-sys-corner-extra-small— 4px--mat-sys-corner-small— 8px--mat-sys-corner-medium— 12px (стандарт) ✅--mat-sys-corner-large— 16px--mat-sys-corner-extra-large— 28px--mat-sys-corner-full— 9999px (круглые элементы)
Spacing (Padding & Margin)
Используем кастомные переменные для отступов:
.card {
padding: var(--spacing-md); // 16px - стандарт
}
.container {
padding: var(--spacing-xl); // 32px - большой
}
.section {
margin-bottom: var(--spacing-lg); // 24px
}
.small-badge {
padding: var(--spacing-xs); // 4px
}Доступные значения:
--spacing-xs— 4px--spacing-sm— 8px--spacing-md— 16px (стандарт) ✅--spacing-lg— 24px--spacing-xl— 32px (большой) ✅--spacing-2xl— 48px
Цвета (Material 3)
НИКОГДА не используем хардкод цвета. ВСЕГДА только переменные:
// ✅ Правильно
.button {
background: var(--mat-sys-primary);
color: var(--mat-sys-on-primary);
}
.card {
background: var(--mat-sys-surface-container);
color: var(--mat-sys-on-surface);
}
// ❌ Неправильно
.button {
background: #8c68cb;
color: white;
}Основные цветовые токены:
// Primary (фиолетовый)
--mat-sys-primary: #8c68cb;
--mat-sys-on-primary: #ffffff;
--mat-sys-primary-container: #e9ddff;
// Surface (серые)
--mat-sys-surface: #ffffff;
--mat-sys-surface-container: #f5f5f5;
--mat-sys-surface-container-high: #eeeeee;
--mat-sys-on-surface: #1c1b1f;
--mat-sys-on-surface-variant: #424242;
// Error (красный)
--mat-sys-error: #ee6352;
--mat-sys-error-container: #ffdad4;
// Tertiary (зеленый)
--mat-sys-tertiary: #49ca8d;
--mat-sys-tertiary-container: #c8f5dc;
// Outline
--mat-sys-outline: #bdbdbd;
--mat-sys-outline-variant: #e0e0e0;Типографика (Material 3)
Используем типографические токены вместо font-size и font-weight:
// ✅ Правильно
.title {
font: var(--mat-sys-headline-large); // 32px, 600 weight
}
.subtitle {
font: var(--mat-sys-title-medium); // 16px, 500 weight
}
.body-text {
font: var(--mat-sys-body-medium); // 14px, 400 weight
}
// ❌ Неправильно
.title {
font-size: 32px;
font-weight: 600;
}Доступные токены:
--mat-sys-headline-large— 32px--mat-sys-headline-medium— 28px--mat-sys-headline-small— 24px--mat-sys-title-large— 22px--mat-sys-title-medium— 16px--mat-sys-title-small— 14px--mat-sys-body-large— 16px--mat-sys-body-medium— 14px--mat-sys-body-small— 12px--mat-sys-label-large— 14px--mat-sys-label-medium— 12px--mat-sys-label-small— 11px
Миксины
@include component()
Основной миксин для компонента. Создаёт :host с display: block.
@use "../../../../../../assets/scss/abstractions/index" as *;
@include component() {
.container {
padding: var(--spacing-md);
}
}С параметром display:
@include component(flex) {
// :host { display: flex; }
flex-direction: column;
}@include mobile()
Медиа-запрос для мобильных устройств (max-width: 55.5em / 888px).
.sidebar {
width: 300px;
@include mobile() {
width: 100%;
position: fixed;
}
}@include breakpoint($n)
Универсальный миксин для брейкпоинтов (1-5).
.container {
padding: 32px;
@include breakpoint(3) {
// max-width: 55.5em (888px)
padding: 16px;
}
@include breakpoint(1) {
// max-width: 36em (576px)
padding: 8px;
}
}Доступные брейкпоинты:
breakpoint(1)— max-width: 36em (576px)breakpoint(2)— max-width: 48em (768px)breakpoint(3)— max-width: 55.5em (888px) - используется вmobile()breakpoint(4)— max-width: 75em (1200px)breakpoint(5)— max-width: 87.5em (1400px)
@include noWrap()
Обрезка текста с многоточием.
.username {
@include noWrap();
max-width: 200px;
}
// Результат:
// white-space: nowrap;
// overflow: hidden;
// text-overflow: ellipsis;@include noScrollbar()
Скрывает scrollbar.
.content {
@include noScrollbar();
}@include hideOnMobile()
Скрывает элемент на мобильных устройствах.
.desktop-menu {
@include hideOnMobile();
}@include hideOnDesktop()
Показывает элемент только на мобильных.
.mobile-menu {
@include hideOnDesktop();
}Структура SCSS файла
Правильная структура компонента:
// 1. Импорт abstractions
@use "../../../../../../assets/scss/abstractions/index" as *;
// 2. Миксин component()
@include component() {
// 3. Корневой элемент компонента
.my-component {
display: flex;
padding: var(--spacing-md);
border-radius: var(--mat-sys-corner-medium);
background: var(--mat-sys-surface-container);
// 4. Вложенные элементы
.header {
padding: var(--spacing-sm);
font: var(--mat-sys-title-large);
.title {
color: var(--mat-sys-on-surface);
}
}
.content {
flex: 1;
padding: var(--spacing-md);
.text {
font: var(--mat-sys-body-medium);
@include noWrap();
}
}
// 5. Медиа-запросы последними
@include mobile() {
flex-direction: column;
padding: var(--spacing-sm);
}
}
}Примеры
Карточка с адаптивностью
@use "../../../../../../assets/scss/abstractions/index" as *;
@include component() {
.card {
background: var(--mat-sys-surface);
border-radius: var(--mat-sys-corner-medium);
padding: var(--spacing-xl);
box-shadow: var(--mat-sys-level1);
.card-header {
display: flex;
justify-content: space-between;
margin-bottom: var(--spacing-md);
.title {
font: var(--mat-sys-headline-small);
color: var(--mat-sys-on-surface);
}
.actions {
display: flex;
gap: var(--spacing-sm);
}
}
.card-content {
font: var(--mat-sys-body-medium);
color: var(--mat-sys-on-surface-variant);
}
@include mobile() {
padding: var(--spacing-md);
.card-header {
flex-direction: column;
gap: var(--spacing-sm);
}
}
}
}Список с overflow
@use "../../../../../../assets/scss/abstractions/index" as *;
@include component() {
.list-container {
height: 400px;
@include noScrollbar();
.list-item {
padding: var(--spacing-sm) var(--spacing-md);
border-radius: var(--mat-sys-corner-small);
cursor: pointer;
&:hover {
background: var(--mat-sys-surface-container);
}
.item-title {
font: var(--mat-sys-label-large);
@include noWrap();
}
.item-subtitle {
font: var(--mat-sys-label-small);
color: var(--mat-sys-on-surface-variant);
@include noWrap();
}
}
}
}Кнопка с состояниями
@use "../../../../../../assets/scss/abstractions/index" as *;
@include component() {
.custom-button {
padding: var(--spacing-sm) var(--spacing-md);
border-radius: var(--mat-sys-corner-small);
background: var(--mat-sys-primary);
color: var(--mat-sys-on-primary);
font: var(--mat-sys-label-large);
cursor: pointer;
border: none;
&:hover {
background: var(--mat-sys-primary-container);
color: var(--mat-sys-on-primary-container);
}
&:disabled {
background: var(--mat-sys-surface-container);
color: var(--mat-sys-on-surface-variant);
cursor: not-allowed;
}
&.error {
background: var(--mat-sys-error);
color: var(--mat-sys-on-error);
}
}
}Чек-лист перед коммитом
- [ ] Используется
@include component() - [ ] Все стили вложенные (не плоские селекторы)
- [ ] Используются переменные для цветов (
var(--mat-sys-*)) - [ ] Используются переменные для
border-radius(var(--mat-sys-corner-*)) - [ ] Используются переменные для
padding/margin(var(--spacing-*)) - [ ] Используются типографические токены вместо
font-size/font-weight - [ ] Миксины
@include mobile(),@include noWrap()где нужно - [ ] Нет хардкода цветов, размеров шрифтов
- [ ] Stylelint проходит без ошибок
Что НЕ делать
❌ Плоские селекторы:
.card {
}
.card-header {
}
.card-title {
}❌ Хардкод значений:
.card {
border-radius: 12px;
padding: 16px;
background: #8c68cb;
font-size: 16px;
font-weight: 600;
}❌ Без @include component():
@use "../../scss/index" as *;
.my-component {
display: flex;
}❌ Игнорирование миксинов:
.text {
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
// Вместо @include noWrap()
}Следующие шаги
- Правила кода — TypeScript правила
- Качество кода — ESLint, Stylelint
- Архитектура — структура проекта