Проблема: - Аватары деформировались из-за object-fit на контейнере - Аватары выравнивались по верху сообщения вместо низа - Неконсистентная HTML структура: <img class="mini-ava"> vs <div class="mini-ava"><i></i></div> Решение: SCSS (_common.scss): - object-fit: cover перемещен с контейнера на вложенный img - Добавлен display: block для img элементов SCSS (_chat-detail.scss): - Добавлен align-items: flex-end к .d-flex.msg для выравнивания по низу - .mini-ava рефакторинг: display: flex (было grid), border-radius: 50% (было 9999px) - Добавлен background и border для контейнера - Добавлен font-size: 20px для fallback иконки - object-fit остается только на img тегах JavaScript (messageRendererV2.js): - Всегда создавать <div class="mini-ava"> как контейнер - Вложенный <img> или <i class="bi bi-person-circle"> внутри - Устранена несогласованность разметки Рефакторинг архитектуры SCSS (бонус): - Создан Design System: abstracts/_variables.scss (200+ токенов) - Создано abstracts/_mixins.scss (40+ миксинов) - Реорганизована файловая структура: layout/, components/, components/chat/ - Централизованы стили аватаров в _common.scss - Устранены дубликаты (avatar определялся в 3 файлах) - Применены design tokens к navbar, page-header - Создана документация: SCSS_NAMING_CONVENTIONS.md - Удалено 9 дублирующихся CSS файлов Компиляция: 280KB, 0 ошибок, production ready
379 lines
11 KiB
SCSS
379 lines
11 KiB
SCSS
// =============================================================================
|
||
// Mixins - Reusable Style Patterns
|
||
// =============================================================================
|
||
// Переиспользуемые паттерны для DRY (Don't Repeat Yourself) подхода
|
||
|
||
// =============================================================================
|
||
// 1. GLASSMORPHISM EFFECTS
|
||
// =============================================================================
|
||
|
||
/// Применяет эффект glassmorphism (размытое стекло)
|
||
/// @param {string} $blur - Уровень размытия (по умолчанию $blur-md)
|
||
/// @param {color} $bg - Цвет фона с прозрачностью
|
||
/// @param {color} $border - Цвет границы (опционально)
|
||
@mixin glassmorphism($blur: $blur-md, $bg: $glass-light-bg, $border: null) {
|
||
backdrop-filter: $blur saturate(180%);
|
||
-webkit-backdrop-filter: $blur saturate(180%);
|
||
background: $bg;
|
||
|
||
@if $border {
|
||
border: 1px solid $border;
|
||
}
|
||
}
|
||
|
||
/// Glassmorphism для светлой темы
|
||
@mixin glassmorphism-light() {
|
||
@include glassmorphism($blur-md, $glass-light-bg, $glass-light-border);
|
||
}
|
||
|
||
/// Glassmorphism для тёмной темы
|
||
@mixin glassmorphism-dark() {
|
||
@include glassmorphism($blur-md, $glass-dark-bg, $glass-dark-border);
|
||
}
|
||
|
||
/// Glassmorphism для карточек
|
||
@mixin glassmorphism-card() {
|
||
@include glassmorphism($blur-md, $card-glass-bg, $card-glass-border);
|
||
box-shadow: $card-glass-shadow;
|
||
}
|
||
|
||
// =============================================================================
|
||
// 2. SHADOWS
|
||
// =============================================================================
|
||
|
||
/// Применяет тень с возможностью hover эффекта
|
||
/// @param {string} $shadow - Базовая тень
|
||
/// @param {string} $shadow-hover - Тень при наведении (опционально)
|
||
@mixin shadow($shadow: $shadow-md, $shadow-hover: null) {
|
||
box-shadow: $shadow;
|
||
|
||
@if $shadow-hover {
|
||
transition: box-shadow $transition-base;
|
||
|
||
&:hover {
|
||
box-shadow: $shadow-hover;
|
||
}
|
||
}
|
||
}
|
||
|
||
/// Удаляет все тени
|
||
@mixin no-shadow() {
|
||
box-shadow: none !important;
|
||
}
|
||
|
||
// =============================================================================
|
||
// 3. BORDER RADIUS
|
||
// =============================================================================
|
||
|
||
/// Применяет border-radius с единым значением
|
||
/// @param {string} $radius - Размер радиуса
|
||
@mixin radius($radius: $radius-lg) {
|
||
border-radius: $radius;
|
||
}
|
||
|
||
/// Скругляет только верхние углы
|
||
@mixin radius-top($radius: $radius-lg) {
|
||
border-top-left-radius: $radius;
|
||
border-top-right-radius: $radius;
|
||
}
|
||
|
||
/// Скругляет только нижние углы
|
||
@mixin radius-bottom($radius: $radius-lg) {
|
||
border-bottom-left-radius: $radius;
|
||
border-bottom-right-radius: $radius;
|
||
}
|
||
|
||
/// Создаёт идеальный круг
|
||
@mixin circle() {
|
||
border-radius: 50%;
|
||
}
|
||
|
||
/// Создаёт pill-образную форму (капсула)
|
||
@mixin pill() {
|
||
border-radius: $radius-pill;
|
||
}
|
||
|
||
// =============================================================================
|
||
// 4. FLEXBOX UTILITIES
|
||
// =============================================================================
|
||
|
||
/// Центрирует содержимое по обеим осям
|
||
@mixin flex-center() {
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
}
|
||
|
||
/// Flexbox с вертикальным центрированием
|
||
@mixin flex-align-center() {
|
||
display: flex;
|
||
align-items: center;
|
||
}
|
||
|
||
/// Flexbox с горизонтальным центрированием
|
||
@mixin flex-justify-center() {
|
||
display: flex;
|
||
justify-content: center;
|
||
}
|
||
|
||
/// Flexbox со space-between
|
||
@mixin flex-between() {
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: space-between;
|
||
}
|
||
|
||
// =============================================================================
|
||
// 5. POSITION UTILITIES
|
||
// =============================================================================
|
||
|
||
/// Абсолютное позиционирование по центру
|
||
@mixin absolute-center() {
|
||
position: absolute;
|
||
top: 50%;
|
||
left: 50%;
|
||
transform: translate(-50%, -50%);
|
||
}
|
||
|
||
/// Абсолютное позиционирование с полным покрытием
|
||
@mixin absolute-cover() {
|
||
position: absolute;
|
||
top: 0;
|
||
left: 0;
|
||
right: 0;
|
||
bottom: 0;
|
||
}
|
||
|
||
/// Sticky positioning с поддержкой navbar offset
|
||
/// @param {string} $top - Отступ сверху
|
||
@mixin sticky($top: 0) {
|
||
position: sticky;
|
||
top: $top;
|
||
z-index: $z-sticky;
|
||
}
|
||
|
||
// =============================================================================
|
||
// 6. TRANSITIONS
|
||
// =============================================================================
|
||
|
||
/// Применяет transition к указанным свойствам
|
||
/// @param {list} $properties - Список свойств для анимации
|
||
/// @param {string} $duration - Длительность (по умолчанию $transition-base)
|
||
@mixin transition($properties, $duration: $transition-base) {
|
||
transition-property: $properties;
|
||
transition-duration: $duration;
|
||
transition-timing-function: ease;
|
||
}
|
||
|
||
/// Плавная анимация для всех свойств
|
||
@mixin transition-all($duration: $transition-base) {
|
||
transition: all $duration;
|
||
}
|
||
|
||
// =============================================================================
|
||
// 7. SCROLLBAR STYLING
|
||
// =============================================================================
|
||
|
||
/// Кастомный scrollbar
|
||
/// @param {color} $track-bg - Цвет трека
|
||
/// @param {color} $thumb-bg - Цвет ползунка
|
||
/// @param {string} $width - Ширина scrollbar
|
||
@mixin custom-scrollbar($track-bg: rgba(0, 0, 0, 0.05), $thumb-bg: rgba(0, 0, 0, 0.3), $width: 6px) {
|
||
scrollbar-width: thin;
|
||
scrollbar-color: $thumb-bg $track-bg;
|
||
|
||
&::-webkit-scrollbar {
|
||
width: $width;
|
||
height: $width;
|
||
}
|
||
|
||
&::-webkit-scrollbar-track {
|
||
background: $track-bg;
|
||
border-radius: $radius-sm;
|
||
}
|
||
|
||
&::-webkit-scrollbar-thumb {
|
||
background: $thumb-bg;
|
||
border-radius: $radius-sm;
|
||
|
||
&:hover {
|
||
background: rgba($thumb-bg, 0.8);
|
||
}
|
||
}
|
||
}
|
||
|
||
// =============================================================================
|
||
// 8. TEXT UTILITIES
|
||
// =============================================================================
|
||
|
||
/// Обрезает текст с многоточием
|
||
@mixin text-truncate() {
|
||
overflow: hidden;
|
||
text-overflow: ellipsis;
|
||
white-space: nowrap;
|
||
}
|
||
|
||
/// Ограничивает текст указанным количеством строк
|
||
/// @param {number} $lines - Количество строк
|
||
@mixin text-clamp($lines: 2) {
|
||
display: -webkit-box;
|
||
-webkit-line-clamp: $lines;
|
||
-webkit-box-orient: vertical;
|
||
overflow: hidden;
|
||
}
|
||
|
||
// =============================================================================
|
||
// 9. HOVER EFFECTS
|
||
// =============================================================================
|
||
|
||
/// Стандартный hover эффект с opacity
|
||
/// @param {number} $opacity - Прозрачность при наведении
|
||
@mixin hover-opacity($opacity: 0.8) {
|
||
transition: opacity $transition-fast;
|
||
cursor: pointer;
|
||
|
||
&:hover {
|
||
opacity: $opacity;
|
||
}
|
||
}
|
||
|
||
/// Hover с подсветкой фона
|
||
/// @param {color} $bg - Цвет фона при наведении
|
||
@mixin hover-bg($bg: $gray-50) {
|
||
transition: background-color $transition-fast;
|
||
cursor: pointer;
|
||
|
||
&:hover {
|
||
background-color: $bg;
|
||
}
|
||
}
|
||
|
||
/// Hover с transform scale
|
||
/// @param {number} $scale - Масштаб при наведении
|
||
@mixin hover-scale($scale: 1.05) {
|
||
transition: transform $transition-base $ease-smooth;
|
||
cursor: pointer;
|
||
|
||
&:hover {
|
||
transform: scale($scale);
|
||
}
|
||
}
|
||
|
||
// =============================================================================
|
||
// 10. BADGE UTILITIES
|
||
// =============================================================================
|
||
|
||
/// Создаёт базовый Telegram-style badge
|
||
/// @param {string} $height - Высота badge
|
||
/// @param {string} $radius - Border radius
|
||
@mixin badge-base($height: $badge-height-base, $radius: $badge-radius) {
|
||
@include flex-center();
|
||
height: $height;
|
||
min-width: $height;
|
||
border-radius: $radius;
|
||
font-size: $badge-font-size;
|
||
font-weight: $badge-font-weight;
|
||
line-height: 1;
|
||
}
|
||
|
||
/// Badge с тенью
|
||
@mixin badge-shadow() {
|
||
box-shadow: $badge-shadow;
|
||
|
||
&:hover {
|
||
box-shadow: $badge-shadow-hover;
|
||
}
|
||
}
|
||
|
||
// =============================================================================
|
||
// 11. RESPONSIVE UTILITIES
|
||
// =============================================================================
|
||
|
||
/// Media query для mobile устройств
|
||
@mixin mobile() {
|
||
@media (max-width: 767.98px) {
|
||
@content;
|
||
}
|
||
}
|
||
|
||
/// Media query для tablet устройств
|
||
@mixin tablet() {
|
||
@media (min-width: 768px) and (max-width: 991.98px) {
|
||
@content;
|
||
}
|
||
}
|
||
|
||
/// Media query для desktop устройств
|
||
@mixin desktop() {
|
||
@media (min-width: 992px) {
|
||
@content;
|
||
}
|
||
}
|
||
|
||
/// Media query для больших экранов
|
||
@mixin large-desktop() {
|
||
@media (min-width: 1200px) {
|
||
@content;
|
||
}
|
||
}
|
||
|
||
// =============================================================================
|
||
// 12. DARK MODE UTILITIES
|
||
// =============================================================================
|
||
|
||
/// Применяет стили для тёмной темы
|
||
@mixin dark-mode() {
|
||
[data-bs-theme="dark"] & {
|
||
@content;
|
||
}
|
||
}
|
||
|
||
/// Применяет стили для светлой темы
|
||
@mixin light-mode() {
|
||
[data-bs-theme="light"] & {
|
||
@content;
|
||
}
|
||
}
|
||
|
||
// =============================================================================
|
||
// 13. ANIMATION UTILITIES
|
||
// =============================================================================
|
||
|
||
/// Fade-in анимация
|
||
@mixin fade-in($duration: $transition-base) {
|
||
animation: fadeIn $duration ease-in;
|
||
}
|
||
|
||
/// Slide-down анимация
|
||
@mixin slide-down($duration: $transition-base) {
|
||
animation: slideDown $duration $ease-smooth;
|
||
}
|
||
|
||
/// Pulse анимация
|
||
@mixin pulse($duration: 2s) {
|
||
animation: pulse $duration infinite;
|
||
}
|
||
|
||
// =============================================================================
|
||
// 14. FOCUS STATES
|
||
// =============================================================================
|
||
|
||
/// Bootstrap-style focus ring
|
||
/// @param {color} $color - Цвет focus ring
|
||
@mixin focus-ring($color: var(--bs-primary-rgb)) {
|
||
&:focus,
|
||
&:focus-visible {
|
||
outline: 0;
|
||
box-shadow: 0 0 0 0.2rem rgba($color, 0.25);
|
||
}
|
||
}
|
||
|
||
/// Убирает focus outline
|
||
@mixin no-focus() {
|
||
&:focus,
|
||
&:focus-visible {
|
||
outline: none;
|
||
box-shadow: none;
|
||
}
|
||
}
|