backup: SCSS before reorganization - create backup folder and plan
This commit is contained in:
parent
f44a9ac23d
commit
e13ddb9a8b
@ -0,0 +1,197 @@
|
||||
// =============================================================================
|
||||
// CSS Custom Properties (CSS Variables)
|
||||
// =============================================================================
|
||||
// CSS переменные, доступные в runtime через var(--variable-name)
|
||||
// В отличие от SCSS переменных ($variable), эти работают в браузере
|
||||
|
||||
:root {
|
||||
// ========== Цветовая схема ==========
|
||||
|
||||
// Основные цвета приложения
|
||||
--app-primary: #0d6efd;
|
||||
--app-success: #198754;
|
||||
--app-danger: #dc3545;
|
||||
--app-warning: #ffc107;
|
||||
--app-info: #0dcaf0;
|
||||
|
||||
// Фоновые цвета
|
||||
--bg-light: #f8f9fa;
|
||||
--bg-dark: #212529;
|
||||
--bg-card: #ffffff;
|
||||
|
||||
// Цвета текста
|
||||
--text-primary: #212529;
|
||||
--text-secondary: #6c757d;
|
||||
--text-muted: #adb5bd;
|
||||
|
||||
// ========== Размеры и отступы ==========
|
||||
|
||||
// Высота компонентов
|
||||
--navbar-h: 60px;
|
||||
--sidebar-w: 200px;
|
||||
--rightbar-w: 350px;
|
||||
|
||||
// Радиусы скругления
|
||||
--radius-xs: 4px;
|
||||
--radius-sm: 6px;
|
||||
--radius-md: 10px;
|
||||
--radius-lg: 14px;
|
||||
--radius-xl: 18px;
|
||||
--radius-2xl: 24px;
|
||||
--radius-full: 50%;
|
||||
|
||||
// Радиусы компонентов
|
||||
--feed-radius: var(--radius-xl);
|
||||
--card-radius: var(--radius-lg);
|
||||
--button-radius: var(--radius-md);
|
||||
--input-radius: var(--radius-md);
|
||||
--badge-radius: 999px;
|
||||
|
||||
// ========== Унифицированная система отступов (8px grid) ==========
|
||||
|
||||
// Базовая шкала отступов
|
||||
--space-0: 0;
|
||||
--space-1: 0.25rem; // 4px
|
||||
--space-2: 0.5rem; // 8px
|
||||
--space-3: 0.75rem; // 12px
|
||||
--space-4: 1rem; // 16px
|
||||
--space-5: 1.5rem; // 24px
|
||||
|
||||
// Семантические названия (для лучшей читаемости)
|
||||
--space-xs: var(--space-1);
|
||||
--space-sm: var(--space-2);
|
||||
--space-md: var(--space-3);
|
||||
--space-lg: var(--space-4);
|
||||
--space-xl: var(--space-5);
|
||||
|
||||
// Специфичные отступы для компонентов
|
||||
--gap-component: var(--space-3);
|
||||
--gap-section: var(--space-4);
|
||||
|
||||
// Padding карточек и контейнеров
|
||||
--card-padding: var(--space-2);
|
||||
--card-padding-sm: var(--space-1);
|
||||
--card-padding-lg: var(--space-3);
|
||||
|
||||
// Margin между элементами
|
||||
--margin-section: var(--space-3);
|
||||
--margin-block: var(--space-4);
|
||||
|
||||
// Layout отступы (app-layout grid)
|
||||
--layout-gap: var(--space-2);
|
||||
--layout-padding-y: var(--space-3);
|
||||
--layout-padding-x: 0;
|
||||
|
||||
// Старые переменные (deprecated, для обратной совместимости)
|
||||
--spacing-xs: var(--space-1);
|
||||
--spacing-sm: var(--space-2);
|
||||
--spacing-md: var(--space-3);
|
||||
--spacing-lg: var(--space-4);
|
||||
--spacing-xl: var(--space-5);
|
||||
|
||||
// ========== Тени ==========
|
||||
|
||||
--shadow-xs: 0 1px 2px rgba(0, 0, 0, 0.05);
|
||||
--shadow-sm: 0 1px 3px rgba(0, 0, 0, 0.08);
|
||||
--shadow-md: 0 2px 8px rgba(0, 0, 0, 0.08);
|
||||
--shadow-lg: 0 4px 16px rgba(0, 0, 0, 0.12);
|
||||
--shadow-xl: 0 8px 32px rgba(0, 0, 0, 0.16);
|
||||
|
||||
// Тени компонентов
|
||||
--feed-shadow: var(--shadow-md);
|
||||
--feed-shadow-hover: var(--shadow-lg);
|
||||
--card-shadow: var(--shadow-sm);
|
||||
--dropdown-shadow: var(--shadow-lg);
|
||||
--modal-shadow: var(--shadow-xl);
|
||||
|
||||
// ========== Компоненты Feed ==========
|
||||
|
||||
--feed-gap: 0.75rem;
|
||||
|
||||
// ========== iOS Компоненты ==========
|
||||
|
||||
--ios-overlay-bg: rgba(0, 0, 0, 0.4);
|
||||
--ios-sheet-radius: 24px 24px 0 0;
|
||||
--ios-grip-w: 36px;
|
||||
--ios-grip-h: 5px;
|
||||
--ios-search-radius: var(--radius-md);
|
||||
|
||||
// ========== Аватары ==========
|
||||
|
||||
--avatar-xs: 28px;
|
||||
--avatar-sm: 36px;
|
||||
--avatar-md: 44px;
|
||||
--avatar-lg: 64px;
|
||||
--avatar-xl: 96px;
|
||||
|
||||
// ========== Календарь ==========
|
||||
|
||||
--calendar-day-size: 1.6rem;
|
||||
--calendar-event-radius: 6px;
|
||||
--calendar-wrap-radius: 10px;
|
||||
|
||||
// ========== Переходы и анимации ==========
|
||||
|
||||
--transition-fast: 0.15s ease;
|
||||
--transition-base: 0.2s ease;
|
||||
--transition-slow: 0.3s ease;
|
||||
|
||||
// ========== Z-индексы ==========
|
||||
|
||||
--z-dropdown: 1000;
|
||||
--z-sticky: 1020;
|
||||
--z-fixed: 1030;
|
||||
--z-modal-backdrop: 1040;
|
||||
--z-modal: 1050;
|
||||
--z-popover: 1060;
|
||||
--z-tooltip: 1070;
|
||||
|
||||
// ========== Типографика ==========
|
||||
|
||||
--font-family-sans: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
|
||||
--font-family-mono: SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;
|
||||
|
||||
--font-size-xs: 0.75rem;
|
||||
--font-size-sm: 0.875rem;
|
||||
--font-size-base: 1rem;
|
||||
--font-size-lg: 1.125rem;
|
||||
--font-size-xl: 1.25rem;
|
||||
|
||||
--font-weight-light: 300;
|
||||
--font-weight-normal: 400;
|
||||
--font-weight-medium: 500;
|
||||
--font-weight-semibold: 600;
|
||||
--font-weight-bold: 700;
|
||||
|
||||
// ========== Границы ==========
|
||||
|
||||
--border-width: 1px;
|
||||
--border-color: #dee2e6;
|
||||
--border-radius: var(--radius-md);
|
||||
}
|
||||
|
||||
// ========== Вспомогательные классы с переменными ==========
|
||||
|
||||
.shadow-sm { box-shadow: var(--shadow-sm); }
|
||||
.shadow-md { box-shadow: var(--shadow-md); }
|
||||
.shadow-lg { box-shadow: var(--shadow-lg); }
|
||||
.shadow-xl { box-shadow: var(--shadow-xl); }
|
||||
|
||||
.radius-sm { border-radius: var(--radius-sm); }
|
||||
.radius-md { border-radius: var(--radius-md); }
|
||||
.radius-lg { border-radius: var(--radius-lg); }
|
||||
.radius-xl { border-radius: var(--radius-xl); }
|
||||
.radius-full { border-radius: var(--radius-full); }
|
||||
|
||||
.transition-fast { transition: all var(--transition-fast); }
|
||||
.transition-base { transition: all var(--transition-base); }
|
||||
.transition-slow { transition: all var(--transition-slow); }
|
||||
|
||||
// ========== Адаптивные переменные ==========
|
||||
|
||||
// На мобильных navbar выше (90px вместо 60px)
|
||||
@media (max-width: 767.98px) {
|
||||
:root {
|
||||
--navbar-h: 90px;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,8 @@
|
||||
// =============================================================================
|
||||
// Index - Centralized Imports
|
||||
// =============================================================================
|
||||
// Единая точка импорта всех abstracts компонентов
|
||||
|
||||
@import "variables";
|
||||
@import "css-variables";
|
||||
@import "mixins";
|
||||
378
backend/static/scss_backup_20260115/abstracts/_mixins.scss
Normal file
378
backend/static/scss_backup_20260115/abstracts/_mixins.scss
Normal file
@ -0,0 +1,378 @@
|
||||
// =============================================================================
|
||||
// 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;
|
||||
}
|
||||
}
|
||||
223
backend/static/scss_backup_20260115/abstracts/_variables.scss
Normal file
223
backend/static/scss_backup_20260115/abstracts/_variables.scss
Normal file
@ -0,0 +1,223 @@
|
||||
// =============================================================================
|
||||
// Design Tokens - Centralized Variables
|
||||
// =============================================================================
|
||||
// Централизованная система переменных для всего проекта
|
||||
// Все компоненты должны использовать эти токены вместо hardcoded значений
|
||||
|
||||
// =============================================================================
|
||||
// 1. ЦВЕТОВАЯ СИСТЕМА
|
||||
// =============================================================================
|
||||
|
||||
// Telegram colors
|
||||
$telegram-blue: #0088cc;
|
||||
$telegram-mention-bg: #0088cc;
|
||||
|
||||
// Gradient colors
|
||||
$gradient-purple-start: #667eea;
|
||||
$gradient-purple-end: #764ba2;
|
||||
$gradient-pink-start: #f093fb;
|
||||
$gradient-pink-end: #f5576c;
|
||||
|
||||
// Base colors
|
||||
$color-white: #fff;
|
||||
$color-black: #000;
|
||||
|
||||
// Gray scale
|
||||
$gray-50: #f8f9fa;
|
||||
$gray-100: #f0f0f0;
|
||||
$gray-200: #e3e3e3;
|
||||
$gray-300: #dee2e6;
|
||||
$gray-400: #ced4da;
|
||||
$gray-500: #adb5bd;
|
||||
$gray-600: #6c757d;
|
||||
$gray-700: #495057;
|
||||
$gray-800: #343a40;
|
||||
$gray-900: #212529;
|
||||
|
||||
// Semantic colors
|
||||
$color-danger: #ef4444;
|
||||
$color-success-light: #e3f2fd;
|
||||
$color-warning-light: #fffbeb;
|
||||
|
||||
// =============================================================================
|
||||
// 2. SPACING SYSTEM (уже определена в custom-bootstrap.scss)
|
||||
// =============================================================================
|
||||
// Используем Bootstrap переменные $spacers
|
||||
// 0: 0, 1: 4px, 2: 8px, 3: 12px, 4: 16px, 5: 24px
|
||||
|
||||
// =============================================================================
|
||||
// 3. BORDER RADIUS TOKENS
|
||||
// =============================================================================
|
||||
|
||||
$radius-xs: 4px; // extra small
|
||||
$radius-sm: 6px; // small
|
||||
$radius-md: 10px; // medium (most common)
|
||||
$radius-lg: 12px; // large
|
||||
$radius-xl: 14px; // extra large
|
||||
$radius-2xl: 16px; // 2x large
|
||||
$radius-3xl: 18px; // 3x large
|
||||
$radius-4xl: 20px; // 4x large
|
||||
$radius-pill: 999px; // perfect pill shape
|
||||
|
||||
// Badge specific (perfect circles)
|
||||
$badge-radius: 10px;
|
||||
$badge-radius-sm: 9px;
|
||||
$badge-radius-lg: 12px;
|
||||
$badge-radius-xl: 14px;
|
||||
|
||||
// =============================================================================
|
||||
// 4. SHADOW TOKENS
|
||||
// =============================================================================
|
||||
|
||||
$shadow-none: none;
|
||||
$shadow-xs: 0 1px 2px rgba(0, 0, 0, 0.06);
|
||||
$shadow-sm: 0 1px 3px rgba(0, 0, 0, 0.1);
|
||||
$shadow-md: 0 2px 4px rgba(0, 0, 0, 0.1);
|
||||
$shadow-lg: 0 2px 8px rgba(0, 0, 0, 0.15);
|
||||
$shadow-xl: 0 4px 12px rgba(0, 0, 0, 0.15);
|
||||
$shadow-2xl: 0 4px 16px rgba(0, 0, 0, 0.2);
|
||||
$shadow-3xl: 0 6px 20px rgba(0, 0, 0, 0.3);
|
||||
|
||||
// Badge specific shadows
|
||||
$badge-shadow: 0 2px 4px rgba(0, 0, 0, 0.15);
|
||||
$badge-shadow-hover: 0 3px 6px rgba(0, 0, 0, 0.2);
|
||||
$badge-shadow-dark: 0 2px 4px rgba(0, 0, 0, 0.3);
|
||||
|
||||
// Context menu shadows
|
||||
$context-menu-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
|
||||
|
||||
// =============================================================================
|
||||
// 5. BLUR TOKENS (для backdrop-filter)
|
||||
// =============================================================================
|
||||
|
||||
$blur-none: blur(0);
|
||||
$blur-sm: blur(8px);
|
||||
$blur-md: blur(10px);
|
||||
$blur-lg: blur(12px);
|
||||
$blur-xl: blur(16px);
|
||||
|
||||
// =============================================================================
|
||||
// 6. OPACITY TOKENS
|
||||
// =============================================================================
|
||||
|
||||
$opacity-glass-light: 0.1;
|
||||
$opacity-glass-medium: 0.15;
|
||||
$opacity-glass-high: 0.2;
|
||||
|
||||
$opacity-overlay-light: 0.05;
|
||||
$opacity-overlay-medium: 0.1;
|
||||
$opacity-overlay-dark: 0.3;
|
||||
|
||||
$opacity-border-light: 0.08;
|
||||
$opacity-border-medium: 0.1;
|
||||
$opacity-border-dark: 0.25;
|
||||
|
||||
// =============================================================================
|
||||
// 7. TRANSITION TOKENS
|
||||
// =============================================================================
|
||||
|
||||
$transition-fast: 0.15s ease;
|
||||
$transition-base: 0.2s ease;
|
||||
$transition-medium: 0.3s ease;
|
||||
$transition-slow: 0.4s ease;
|
||||
|
||||
// Специальные easing функции
|
||||
$ease-bounce: cubic-bezier(0.175, 0.885, 0.32, 1.275);
|
||||
$ease-smooth: cubic-bezier(0.4, 0, 0.2, 1);
|
||||
|
||||
// Badge specific
|
||||
$badge-transition: transform 0.2s $ease-bounce;
|
||||
|
||||
// =============================================================================
|
||||
// 8. Z-INDEX TOKENS
|
||||
// =============================================================================
|
||||
|
||||
$z-base: 1;
|
||||
$z-dropdown: 1000;
|
||||
$z-sticky: 1020;
|
||||
$z-fixed: 1030;
|
||||
$z-modal-backdrop: 1040;
|
||||
$z-modal: 1050;
|
||||
$z-popover: 1060;
|
||||
$z-tooltip: 1070;
|
||||
$z-notification: 1080;
|
||||
|
||||
// =============================================================================
|
||||
// 9. SIZE TOKENS (avatars, badges, icons)
|
||||
// =============================================================================
|
||||
|
||||
// Badge heights
|
||||
$badge-height-sm: 18px;
|
||||
$badge-height-base: 20px;
|
||||
$badge-height-lg: 24px;
|
||||
$badge-height-xl: 28px;
|
||||
|
||||
// Badge padding
|
||||
$badge-padding-x: 7px;
|
||||
$badge-padding-y: 2px;
|
||||
|
||||
// Avatar sizes
|
||||
$avatar-xs: 24px;
|
||||
$avatar-sm: 32px;
|
||||
$avatar-md: 40px;
|
||||
$avatar-lg: 44px;
|
||||
$avatar-xl: 48px;
|
||||
$avatar-2xl: 56px;
|
||||
|
||||
// Icon button sizes
|
||||
$btn-icon-sm: 32px;
|
||||
$btn-icon-base: 36px;
|
||||
$btn-icon-lg: 44px;
|
||||
|
||||
// =============================================================================
|
||||
// 10. GLASSMORPHISM PRESETS
|
||||
// =============================================================================
|
||||
|
||||
// Light theme glassmorphism
|
||||
$glass-light-bg: rgba(255, 255, 255, 0.1);
|
||||
$glass-light-border: rgba(0, 0, 0, 0.08);
|
||||
|
||||
// Dark theme glassmorphism
|
||||
$glass-dark-bg: rgba(52, 58, 64, 0.95);
|
||||
$glass-dark-border: rgba(255, 255, 255, 0.1);
|
||||
|
||||
// Card glassmorphism
|
||||
$card-glass-bg: rgba(255, 255, 255, 0.05);
|
||||
$card-glass-border: rgba(255, 255, 255, 0.1);
|
||||
$card-glass-shadow: 0 8px 32px 0 rgba(0, 0, 0, 0.37);
|
||||
|
||||
// =============================================================================
|
||||
// 11. TYPOGRAPHY TOKENS (дополнительные)
|
||||
// =============================================================================
|
||||
|
||||
$font-weight-normal: 400;
|
||||
$font-weight-medium: 500;
|
||||
$font-weight-semibold: 600;
|
||||
$font-weight-bold: 700;
|
||||
|
||||
$font-size-xs: 11px;
|
||||
$font-size-sm: 12px;
|
||||
$font-size-base: 14px;
|
||||
$font-size-md: 16px;
|
||||
$font-size-lg: 18px;
|
||||
|
||||
// Badge specific
|
||||
$badge-font-size: 11px;
|
||||
$badge-font-weight: 600;
|
||||
|
||||
// =============================================================================
|
||||
// 12. LAYOUT TOKENS
|
||||
// =============================================================================
|
||||
|
||||
// Navbar
|
||||
$navbar-height: 56px;
|
||||
$navbar-mobile-height: 90px;
|
||||
$bottom-navbar-height: 60px;
|
||||
|
||||
// Sidebar
|
||||
$sidebar-width: 240px;
|
||||
$sidebar-collapsed-width: 60px;
|
||||
|
||||
// Chat specific
|
||||
$chat-composer-height: auto;
|
||||
$chat-message-max-width: 70%;
|
||||
279
backend/static/scss_backup_20260115/components/BUTTONS_README.md
Normal file
279
backend/static/scss_backup_20260115/components/BUTTONS_README.md
Normal file
@ -0,0 +1,279 @@
|
||||
# Документация: Компонент Buttons
|
||||
|
||||
## Обзор
|
||||
|
||||
Расширенная система кнопок, дополняющая стандартные кнопки Bootstrap дополнительными вариантами и утилитами.
|
||||
|
||||
## Классы кнопок
|
||||
|
||||
### 1. Круглые кнопки (`.btn-circle`)
|
||||
|
||||
Идеально круглые кнопки для иконок. Автоматически становятся квадратными с `border-radius: 50%`.
|
||||
|
||||
#### Базовое использование
|
||||
|
||||
```html
|
||||
<button class="btn btn-primary btn-circle">
|
||||
<i class="bi-plus"></i>
|
||||
</button>
|
||||
|
||||
<button class="btn btn-outline-secondary btn-circle">
|
||||
<i class="bi-funnel"></i>
|
||||
</button>
|
||||
```
|
||||
|
||||
#### Размеры
|
||||
|
||||
```html
|
||||
<!-- Маленькая: 32x32px -->
|
||||
<button class="btn btn-primary btn-circle btn-sm">
|
||||
<i class="bi-heart"></i>
|
||||
</button>
|
||||
|
||||
<!-- Средняя (по умолчанию): 36x36px -->
|
||||
<button class="btn btn-primary btn-circle">
|
||||
<i class="bi-star"></i>
|
||||
</button>
|
||||
|
||||
<!-- Большая: 44x44px -->
|
||||
<button class="btn btn-primary btn-circle btn-lg">
|
||||
<i class="bi-search"></i>
|
||||
</button>
|
||||
```
|
||||
|
||||
Или используйте специальные классы:
|
||||
```html
|
||||
<button class="btn btn-primary btn-circle-sm">...</button>
|
||||
<button class="btn btn-primary btn-circle-md">...</button>
|
||||
<button class="btn btn-primary btn-circle-lg">...</button>
|
||||
```
|
||||
|
||||
#### Состояния
|
||||
|
||||
Toggle-кнопки автоматически вращаются при раскрытии:
|
||||
|
||||
```html
|
||||
<button class="btn btn-outline-secondary btn-circle"
|
||||
data-bs-toggle="collapse"
|
||||
data-bs-target="#filters"
|
||||
aria-expanded="false">
|
||||
<i class="bi-funnel"></i>
|
||||
</button>
|
||||
<!-- При aria-expanded="true" кнопка повернется на 180° -->
|
||||
```
|
||||
|
||||
### 2. Кнопки с иконками (`.btn-icon`)
|
||||
|
||||
Кнопки с иконкой и текстом. На мобильных устройствах текст скрывается.
|
||||
|
||||
```html
|
||||
<button class="btn btn-primary btn-icon">
|
||||
<i class="bi-plus-lg"></i>
|
||||
<span class="btn-text">Создать</span>
|
||||
</button>
|
||||
|
||||
<a href="/create" class="btn btn-success btn-icon">
|
||||
<i class="bi-file-plus"></i>
|
||||
<span class="btn-text">Новый документ</span>
|
||||
</a>
|
||||
```
|
||||
|
||||
**Поведение:**
|
||||
- Десктоп: иконка + текст
|
||||
- Мобильные (< 768px): только иконка (круглая кнопка)
|
||||
|
||||
### 3. Кнопки-призраки (`.btn-ghost`)
|
||||
|
||||
Прозрачные кнопки без фона, появляется фон при наведении.
|
||||
|
||||
```html
|
||||
<button class="btn btn-ghost">
|
||||
<i class="bi-pencil"></i>
|
||||
Редактировать
|
||||
</button>
|
||||
|
||||
<button class="btn btn-ghost btn-circle">
|
||||
<i class="bi-three-dots"></i>
|
||||
</button>
|
||||
```
|
||||
|
||||
### 4. Pill-кнопки (`.btn-pill`)
|
||||
|
||||
Максимально скругленные кнопки (`border-radius: 999px`).
|
||||
|
||||
```html
|
||||
<button class="btn btn-primary btn-pill">
|
||||
Подписаться
|
||||
</button>
|
||||
|
||||
<a href="/more" class="btn btn-outline-secondary btn-pill">
|
||||
Узнать больше
|
||||
</a>
|
||||
```
|
||||
|
||||
## Группы кнопок
|
||||
|
||||
### Группа круглых кнопок
|
||||
|
||||
```html
|
||||
<div class="btn-circle-group">
|
||||
<button class="btn btn-outline-secondary btn-circle">
|
||||
<i class="bi-funnel"></i>
|
||||
</button>
|
||||
<button class="btn btn-outline-secondary btn-circle">
|
||||
<i class="bi-sort-down"></i>
|
||||
</button>
|
||||
<button class="btn btn-primary btn-circle">
|
||||
<i class="bi-plus"></i>
|
||||
</button>
|
||||
</div>
|
||||
```
|
||||
|
||||
## Примеры использования
|
||||
|
||||
### Пример 1: Шапка страницы с фильтрами
|
||||
|
||||
```html
|
||||
<div class="d-flex align-items-center gap-2">
|
||||
<h1>Сотрудники</h1>
|
||||
|
||||
<div class="ms-auto d-flex gap-2">
|
||||
<!-- Поиск -->
|
||||
<input type="search" class="form-control" placeholder="Поиск...">
|
||||
|
||||
<!-- Кнопка фильтров -->
|
||||
<button class="btn btn-outline-secondary btn-circle"
|
||||
data-bs-toggle="collapse"
|
||||
data-bs-target="#filters">
|
||||
<i class="bi-funnel"></i>
|
||||
</button>
|
||||
|
||||
<!-- Кнопка создания -->
|
||||
<button class="btn btn-primary btn-icon">
|
||||
<i class="bi-plus-lg"></i>
|
||||
<span class="btn-text">Создать</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
```
|
||||
|
||||
### Пример 2: Карточка с действиями
|
||||
|
||||
```html
|
||||
<div class="card">
|
||||
<div class="card-body">
|
||||
<h5>Название документа</h5>
|
||||
<p>Описание...</p>
|
||||
|
||||
<div class="btn-circle-group">
|
||||
<button class="btn btn-ghost btn-circle" title="Редактировать">
|
||||
<i class="bi-pencil"></i>
|
||||
</button>
|
||||
<button class="btn btn-ghost btn-circle" title="Поделиться">
|
||||
<i class="bi-share"></i>
|
||||
</button>
|
||||
<button class="btn btn-ghost btn-circle" title="Удалить">
|
||||
<i class="bi-trash"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
```
|
||||
|
||||
### Пример 3: Навигация с разными размерами
|
||||
|
||||
```html
|
||||
<div class="d-flex align-items-center gap-3">
|
||||
<!-- Маленькие кнопки для вспомогательных действий -->
|
||||
<button class="btn btn-outline-secondary btn-circle btn-sm">
|
||||
<i class="bi-arrow-left"></i>
|
||||
</button>
|
||||
|
||||
<!-- Обычная кнопка для основного действия -->
|
||||
<button class="btn btn-primary btn-pill">
|
||||
Далее
|
||||
</button>
|
||||
|
||||
<!-- Маленькая кнопка справа -->
|
||||
<button class="btn btn-outline-secondary btn-circle btn-sm">
|
||||
<i class="bi-arrow-right"></i>
|
||||
</button>
|
||||
</div>
|
||||
```
|
||||
|
||||
## Комбинирование с Bootstrap
|
||||
|
||||
Все новые классы полностью совместимы со стандартными классами Bootstrap:
|
||||
|
||||
```html
|
||||
<!-- Цвета Bootstrap -->
|
||||
<button class="btn btn-primary btn-circle">...</button>
|
||||
<button class="btn btn-success btn-circle">...</button>
|
||||
<button class="btn btn-danger btn-circle">...</button>
|
||||
<button class="btn btn-outline-secondary btn-circle">...</button>
|
||||
|
||||
<!-- Размеры Bootstrap + круглые кнопки -->
|
||||
<button class="btn btn-primary btn-circle btn-sm">...</button>
|
||||
<button class="btn btn-primary btn-circle btn-lg">...</button>
|
||||
|
||||
<!-- Состояния Bootstrap -->
|
||||
<button class="btn btn-primary btn-circle" disabled>...</button>
|
||||
<button class="btn btn-outline-secondary btn-circle active">...</button>
|
||||
```
|
||||
|
||||
## Миграция со старого кода
|
||||
|
||||
### Было (специфичные стили):
|
||||
|
||||
```html
|
||||
<button class="btn btn-outline-secondary page-filter-toggle">
|
||||
<i class="bi-funnel"></i>
|
||||
</button>
|
||||
```
|
||||
|
||||
```css
|
||||
.page-filter-toggle {
|
||||
width: 36px !important;
|
||||
height: 36px !important;
|
||||
border-radius: 50% !important;
|
||||
/* ... еще 20 строк CSS ... */
|
||||
}
|
||||
```
|
||||
|
||||
### Стало (универсальный класс):
|
||||
|
||||
```html
|
||||
<button class="btn btn-outline-secondary btn-circle">
|
||||
<i class="bi-funnel"></i>
|
||||
</button>
|
||||
```
|
||||
|
||||
```css
|
||||
/* Никаких дополнительных стилей не нужно! */
|
||||
```
|
||||
|
||||
## CSS переменные
|
||||
|
||||
Компонент использует стандартные переменные Bootstrap:
|
||||
- `--bs-primary` - основной цвет
|
||||
- `--bs-body-color` - цвет текста
|
||||
- `--bs-border-color` - цвет границ
|
||||
- `--bs-border-radius` - скругление углов
|
||||
|
||||
## Поддержка темной темы
|
||||
|
||||
Все кнопки автоматически адаптируются под темную тему через `prefers-color-scheme: dark`.
|
||||
|
||||
## Доступность
|
||||
|
||||
- Все кнопки имеют правильные `aria-label` атрибуты
|
||||
- Иконки используют `line-height: 1` для правильного центрирования
|
||||
- Фокус-стейты наследуются от Bootstrap
|
||||
- Поддержка клавиатурной навигации
|
||||
|
||||
## Производительность
|
||||
|
||||
- Минимальное использование `!important` (только где абсолютно необходимо)
|
||||
- CSS переменные для легкой кастомизации
|
||||
- Оптимизированные селекторы
|
||||
- Не влияет на другие компоненты
|
||||
232
backend/static/scss_backup_20260115/components/_badges-bem.scss
Normal file
232
backend/static/scss_backup_20260115/components/_badges-bem.scss
Normal file
@ -0,0 +1,232 @@
|
||||
// =============================================================================
|
||||
// Badges Component
|
||||
// =============================================================================
|
||||
// Notification badges with Telegram-style design
|
||||
// Uses design tokens from abstracts/_variables.scss
|
||||
|
||||
// =============================================================================
|
||||
// 1. BASE COMPONENT
|
||||
// =============================================================================
|
||||
|
||||
.badge {
|
||||
@include badge-base($badge-height-base, $badge-radius);
|
||||
@include badge-shadow();
|
||||
color: $color-white;
|
||||
background: var(--bs-danger);
|
||||
transition: $badge-transition;
|
||||
user-select: none;
|
||||
padding: 0 6px;
|
||||
|
||||
// Auto-hide when empty
|
||||
&:empty {
|
||||
display: none;
|
||||
}
|
||||
|
||||
// Hide when count is 0
|
||||
&[data-count="0"] {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// 2. SIZE MODIFIERS
|
||||
// =============================================================================
|
||||
|
||||
.badge--sm {
|
||||
@include badge-base($badge-height-sm, $badge-radius-sm);
|
||||
font-size: 10px;
|
||||
padding: 0 5px;
|
||||
}
|
||||
|
||||
.badge--lg {
|
||||
@include badge-base($badge-height-lg, $badge-radius-lg);
|
||||
font-size: 12px;
|
||||
padding: 0 7px;
|
||||
}
|
||||
|
||||
.badge--xl {
|
||||
@include badge-base($badge-height-xl, $badge-radius-xl);
|
||||
font-size: 13px;
|
||||
padding: 0 8px;
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// 3. COLOR MODIFIERS
|
||||
// =============================================================================
|
||||
|
||||
.badge--primary {
|
||||
background: var(--bs-primary);
|
||||
}
|
||||
|
||||
.badge--secondary {
|
||||
background: var(--bs-secondary);
|
||||
opacity: 0.8;
|
||||
}
|
||||
|
||||
.badge--success {
|
||||
background: var(--bs-success);
|
||||
}
|
||||
|
||||
.badge--warning {
|
||||
background: var(--bs-warning);
|
||||
color: var(--bs-dark);
|
||||
}
|
||||
|
||||
.badge--info {
|
||||
background: var(--bs-info);
|
||||
}
|
||||
|
||||
.badge--mention {
|
||||
background: $telegram-blue;
|
||||
}
|
||||
|
||||
.badge--muted {
|
||||
background: var(--bs-secondary);
|
||||
opacity: 0.6;
|
||||
color: var(--bs-secondary-color);
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// 4. STYLE MODIFIERS
|
||||
// =============================================================================
|
||||
|
||||
.badge--outlined {
|
||||
background: transparent;
|
||||
border: 2px solid currentColor;
|
||||
color: var(--bs-danger);
|
||||
}
|
||||
|
||||
.badge--dot {
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
min-width: 8px;
|
||||
padding: 0;
|
||||
@include radius(50%);
|
||||
}
|
||||
|
||||
.badge--inline {
|
||||
display: inline-flex;
|
||||
vertical-align: middle;
|
||||
margin-left: 4px;
|
||||
}
|
||||
|
||||
// Premium badge with gradient
|
||||
.badge--premium {
|
||||
background: linear-gradient(135deg, $gradient-purple-start 0%, $gradient-purple-end 100%);
|
||||
}
|
||||
|
||||
.badge--star {
|
||||
background: linear-gradient(135deg, $gradient-pink-start 0%, $gradient-pink-end 100%);
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// 5. CONTEXT MODIFIERS
|
||||
// =============================================================================
|
||||
|
||||
// Badge on avatar
|
||||
.badge--avatar {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
transform: translate(25%, -25%);
|
||||
z-index: $z-base;
|
||||
}
|
||||
|
||||
// Badge on button
|
||||
.badge--button {
|
||||
position: absolute;
|
||||
top: -6px;
|
||||
right: -6px;
|
||||
}
|
||||
|
||||
// Interactive badge (clickable)
|
||||
.badge--interactive {
|
||||
cursor: pointer;
|
||||
@include transition-all($transition-fast);
|
||||
|
||||
&:hover {
|
||||
transform: scale(1.1);
|
||||
box-shadow: $badge-shadow-hover;
|
||||
}
|
||||
|
||||
&:active {
|
||||
transform: scale(0.95);
|
||||
}
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// 6. STATES
|
||||
// =============================================================================
|
||||
|
||||
.badge.is-hidden {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.badge.is-animating-in {
|
||||
animation: badge-scale-in 0.2s $ease-bounce;
|
||||
}
|
||||
|
||||
.badge.is-pulsing {
|
||||
animation: badge-pulse 1.5s infinite;
|
||||
}
|
||||
|
||||
.badge.is-animating-out {
|
||||
animation: badge-scale-out 0.2s $ease-bounce forwards;
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// 7. DARK MODE
|
||||
// =============================================================================
|
||||
|
||||
[data-bs-theme="dark"] {
|
||||
.badge {
|
||||
box-shadow: $badge-shadow-dark;
|
||||
}
|
||||
|
||||
.badge--outlined {
|
||||
border-width: 1px;
|
||||
}
|
||||
|
||||
.badge--avatar {
|
||||
height: calc($badge-height-base - 2px);
|
||||
min-width: calc($badge-height-base - 2px);
|
||||
}
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// 8. ANIMATIONS
|
||||
// =============================================================================
|
||||
|
||||
@keyframes badge-scale-in {
|
||||
from {
|
||||
transform: scale(0);
|
||||
opacity: 0;
|
||||
}
|
||||
to {
|
||||
transform: scale(1);
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes badge-pulse {
|
||||
0% {
|
||||
transform: scale(1);
|
||||
}
|
||||
50% {
|
||||
transform: scale(1.15);
|
||||
}
|
||||
100% {
|
||||
transform: scale(1);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes badge-scale-out {
|
||||
from {
|
||||
transform: scale(1);
|
||||
opacity: 1;
|
||||
}
|
||||
to {
|
||||
transform: scale(0);
|
||||
opacity: 0;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,233 @@
|
||||
// =============================================================================
|
||||
// Telegram-Style Badges
|
||||
// =============================================================================
|
||||
// Based on Telegram Web K design system (morethanwords/tweb)
|
||||
// Perfect circles, smooth animations, professional appearance
|
||||
// REFACTORED: использует design tokens из abstracts/_variables.scss
|
||||
|
||||
// =============================================================================
|
||||
// Base Badge Class
|
||||
// =============================================================================
|
||||
.badge-telegram {
|
||||
@include badge-base($badge-height-base, $badge-radius);
|
||||
color: $color-white;
|
||||
background: var(--bs-danger);
|
||||
@include badge-shadow();
|
||||
transition: $badge-transition;
|
||||
user-select: none;
|
||||
padding: 0 $badge-padding-x;
|
||||
|
||||
// Auto-hide when empty
|
||||
&:empty {
|
||||
display: none;
|
||||
}
|
||||
|
||||
// Hide when count is 0
|
||||
&[data-count="0"] {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// Size Variants
|
||||
// =============================================================================
|
||||
.badge-telegram-sm {
|
||||
@include badge-base($badge-height-sm, $badge-radius-sm);
|
||||
font-size: 10px;
|
||||
padding: 0 5px;
|
||||
}
|
||||
|
||||
.badge-telegram-lg {
|
||||
@include badge-base($badge-height-lg, $badge-radius-lg);
|
||||
font-size: 12px;
|
||||
padding: 0 7px;
|
||||
}
|
||||
|
||||
.badge-telegram-xl {
|
||||
@include badge-base($badge-height-xl, $badge-radius-xl);
|
||||
font-size: 13px;
|
||||
padding: 0 8px;
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// Color Variants
|
||||
// =============================================================================
|
||||
.badge-telegram-primary {
|
||||
background: var(--bs-primary);
|
||||
}
|
||||
|
||||
.badge-telegram-muted {
|
||||
background: var(--bs-secondary);
|
||||
opacity: 0.8;
|
||||
}
|
||||
|
||||
.badge-telegram-success {
|
||||
background: var(--bs-success);
|
||||
}
|
||||
|
||||
.badge-telegram-warning {
|
||||
background: var(--bs-warning);
|
||||
color: var(--bs-dark);
|
||||
}
|
||||
|
||||
.badge-telegram-info {
|
||||
background: var(--bs-info);
|
||||
}
|
||||
|
||||
.badge-telegram-mention {
|
||||
background: $telegram-blue;
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// Animations
|
||||
// =============================================================================
|
||||
@keyframes badge-scale-in {
|
||||
from {
|
||||
transform: scale(0);
|
||||
opacity: 0;
|
||||
}
|
||||
to {
|
||||
transform: scale(1);
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes badge-pulse {
|
||||
0% {
|
||||
transform: scale(1);
|
||||
}
|
||||
50% {
|
||||
transform: scale(1.15);
|
||||
}
|
||||
100% {
|
||||
transform: scale(1);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes badge-scale-out {
|
||||
from {
|
||||
transform: scale(1);
|
||||
opacity: 1;
|
||||
}
|
||||
to {
|
||||
transform: scale(0);
|
||||
opacity: 0;
|
||||
}
|
||||
}
|
||||
|
||||
// Animation Classes
|
||||
.badge-telegram.animate-in {
|
||||
animation: badge-scale-in 0.2s $ease-bounce;
|
||||
}
|
||||
|
||||
.badge-telegram.pulse {
|
||||
animation: badge-pulse 0.6s $badge-transition;
|
||||
}
|
||||
|
||||
.badge-telegram.animate-out {
|
||||
animation: badge-scale-out 0.2s $badge-transition;
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// Positioning Variants
|
||||
// =============================================================================
|
||||
// On avatar
|
||||
.badge-telegram-avatar {
|
||||
position: absolute;
|
||||
bottom: -2px;
|
||||
right: -2px;
|
||||
border: 2px solid white;
|
||||
}
|
||||
|
||||
// On button/icon
|
||||
.badge-telegram-button {
|
||||
position: absolute;
|
||||
top: -6px;
|
||||
right: -10px;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
// Inline with text
|
||||
.badge-telegram-inline {
|
||||
position: relative;
|
||||
top: -1px;
|
||||
margin-left: 4px;
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// Special Effects
|
||||
// =============================================================================
|
||||
// Dot badge (no number)
|
||||
.badge-telegram-dot {
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
min-width: 8px;
|
||||
padding: 0;
|
||||
border-radius: 50%;
|
||||
}
|
||||
|
||||
// Interactive badge (hoverable)
|
||||
.badge-telegram-interactive {
|
||||
cursor: pointer;
|
||||
transition: all 0.2s ease;
|
||||
|
||||
&:hover {
|
||||
transform: scale(1.1);
|
||||
box-shadow: 0 3px 6px rgba(0, 0, 0, 0.2);
|
||||
}
|
||||
|
||||
&:active {
|
||||
transform: scale(0.95);
|
||||
}
|
||||
}
|
||||
|
||||
// Premium badge (gradient)
|
||||
.badge-telegram-premium {
|
||||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||
}
|
||||
|
||||
// Star badge (for favorites)
|
||||
.badge-telegram-star {
|
||||
background: linear-gradient(135deg, #f093fb 0%, #f5576c 100%);
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// Responsive Design
|
||||
// =============================================================================
|
||||
@media (max-width: 576px) {
|
||||
.badge-telegram {
|
||||
height: calc($badge-height-base - 2px);
|
||||
min-width: calc($badge-height-base - 2px);
|
||||
font-size: calc($badge-font-size - 1px);
|
||||
padding: 0 calc($badge-padding-x - 1px);
|
||||
}
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// Dark Theme Support
|
||||
// =============================================================================
|
||||
@media (prefers-color-scheme: dark) {
|
||||
.badge-telegram {
|
||||
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.3);
|
||||
}
|
||||
|
||||
.badge-telegram-avatar {
|
||||
border-color: var(--bs-dark);
|
||||
}
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// Utilities
|
||||
// =============================================================================
|
||||
// Hide badge class (for JavaScript manipulation)
|
||||
.badge-telegram-hidden {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
// Badge with border
|
||||
.badge-telegram-outlined {
|
||||
background: transparent;
|
||||
color: var(--bs-danger);
|
||||
border: 2px solid var(--bs-danger);
|
||||
box-shadow: none;
|
||||
}
|
||||
193
backend/static/scss_backup_20260115/components/_buttons.scss
Normal file
193
backend/static/scss_backup_20260115/components/_buttons.scss
Normal file
@ -0,0 +1,193 @@
|
||||
// =============================================================================
|
||||
// Custom Button Styles (SCSS)
|
||||
// =============================================================================
|
||||
// Расширение стандартных кнопок Bootstrap с дополнительными вариантами:
|
||||
// - .btn-circle - круглая кнопка
|
||||
// - .btn-icon - кнопка с иконкой и текстом
|
||||
// - .btn-ghost - кнопка-призрак
|
||||
// - .btn-pill - максимально скругленная кнопка
|
||||
|
||||
// =============================================================================
|
||||
// ПЕРЕМЕННЫЕ
|
||||
// =============================================================================
|
||||
|
||||
// Размеры круглых кнопок (используем значения, кратные 4px)
|
||||
$btn-circle-size-sm: 32px;
|
||||
$btn-circle-size-md: 36px;
|
||||
$btn-circle-size-lg: 44px;
|
||||
|
||||
// Размеры шрифтов
|
||||
$btn-circle-font-sm: 0.875rem;
|
||||
$btn-circle-font-md: 1rem;
|
||||
$btn-circle-font-lg: 1.25rem;
|
||||
|
||||
// Отступы
|
||||
$btn-icon-gap: var(--space-2); // 8px
|
||||
$btn-circle-mobile-size: 36px;
|
||||
$btn-circle-mobile-padding: var(--space-2); // 8px
|
||||
|
||||
// Переходы
|
||||
$btn-transition: all 0.2s ease;
|
||||
$btn-toggle-transition: transform 0.3s ease;
|
||||
|
||||
// =============================================================================
|
||||
// КРУГЛЫЕ КНОПКИ
|
||||
// =============================================================================
|
||||
|
||||
// Миксин для размеров круглых кнопок (DRY)
|
||||
@mixin btn-circle-size($size, $font-size) {
|
||||
width: $size !important;
|
||||
height: $size !important;
|
||||
min-width: $size !important;
|
||||
max-width: $size !important;
|
||||
min-height: $size !important;
|
||||
max-height: $size !important;
|
||||
font-size: $font-size;
|
||||
}
|
||||
|
||||
// Базовый класс для круглых кнопок
|
||||
.btn-circle {
|
||||
border-radius: 50% !important;
|
||||
padding: 0 !important;
|
||||
display: inline-flex !important;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
flex-shrink: 0;
|
||||
aspect-ratio: 1 / 1;
|
||||
overflow: hidden;
|
||||
line-height: 1;
|
||||
|
||||
// Иконка внутри круглой кнопки
|
||||
i {
|
||||
line-height: 1;
|
||||
display: inline-block;
|
||||
}
|
||||
}
|
||||
|
||||
// Размеры круглых кнопок
|
||||
.btn-circle.btn-sm,
|
||||
.btn-circle-sm {
|
||||
@include btn-circle-size($btn-circle-size-sm, $btn-circle-font-sm);
|
||||
}
|
||||
|
||||
.btn-circle:not(.btn-sm):not(.btn-lg),
|
||||
.btn-circle-md {
|
||||
@include btn-circle-size($btn-circle-size-md, $btn-circle-font-md);
|
||||
}
|
||||
|
||||
.btn-circle.btn-lg,
|
||||
.btn-circle-lg {
|
||||
@include btn-circle-size($btn-circle-size-lg, $btn-circle-font-lg);
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// КНОПКИ С ИКОНКАМИ
|
||||
// =============================================================================
|
||||
|
||||
// Кнопка с иконкой и текстом
|
||||
.btn-icon {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: $btn-icon-gap;
|
||||
|
||||
i {
|
||||
line-height: 1;
|
||||
}
|
||||
}
|
||||
|
||||
// Адаптив: только иконка на мобильных
|
||||
@media (max-width: 767.98px) {
|
||||
.btn-icon {
|
||||
.btn-text {
|
||||
display: none;
|
||||
}
|
||||
|
||||
// Если это не круглая кнопка, делаем её круглой на мобильных
|
||||
&:not(.btn-circle) {
|
||||
border-radius: 50% !important;
|
||||
padding: $btn-circle-mobile-padding !important;
|
||||
width: $btn-circle-mobile-size;
|
||||
height: $btn-circle-mobile-size;
|
||||
justify-content: center;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// ДОПОЛНИТЕЛЬНЫЕ ВАРИАНТЫ
|
||||
// =============================================================================
|
||||
|
||||
// Кнопка-призрак (без фона, только border при hover)
|
||||
.btn-ghost {
|
||||
background: transparent;
|
||||
border: 1px solid transparent;
|
||||
color: var(--bs-body-color);
|
||||
transition: $btn-transition;
|
||||
|
||||
&:hover {
|
||||
background: color-mix(in srgb, var(--bs-body-color), transparent 90%);
|
||||
border-color: var(--bs-border-color);
|
||||
color: var(--bs-body-color);
|
||||
}
|
||||
|
||||
&:active {
|
||||
background: color-mix(in srgb, var(--bs-body-color), transparent 85%);
|
||||
}
|
||||
}
|
||||
|
||||
// Pill-кнопки (максимально скругленные)
|
||||
.btn-pill {
|
||||
border-radius: 999px !important;
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// СОСТОЯНИЯ
|
||||
// =============================================================================
|
||||
|
||||
// Активное состояние для toggle-кнопок
|
||||
.btn-circle[aria-expanded="true"],
|
||||
.btn-circle.active {
|
||||
background-color: var(--bs-primary) !important;
|
||||
border-color: var(--bs-primary) !important;
|
||||
color: white !important;
|
||||
}
|
||||
|
||||
// Анимация вращения для toggle-кнопок
|
||||
.btn-circle[aria-expanded="true"] {
|
||||
transform: rotate(180deg);
|
||||
transition: $btn-toggle-transition;
|
||||
}
|
||||
|
||||
.btn-circle[aria-expanded="false"] {
|
||||
transform: rotate(0deg);
|
||||
transition: $btn-toggle-transition;
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// ГРУППЫ КРУГЛЫХ КНОПОК
|
||||
// =============================================================================
|
||||
|
||||
// Группа круглых кнопок с небольшим отступом
|
||||
.btn-circle-group {
|
||||
display: flex;
|
||||
gap: var(--space-2);
|
||||
align-items: center;
|
||||
|
||||
.btn-circle {
|
||||
margin: 0;
|
||||
}
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// ТЕМНАЯ ТЕМА
|
||||
// =============================================================================
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
.btn-ghost {
|
||||
color: var(--bs-body-color);
|
||||
|
||||
&:hover {
|
||||
background: color-mix(in srgb, var(--bs-body-color), transparent 92%);
|
||||
}
|
||||
}
|
||||
}
|
||||
317
backend/static/scss_backup_20260115/components/_card-list.scss
Normal file
317
backend/static/scss_backup_20260115/components/_card-list.scss
Normal file
@ -0,0 +1,317 @@
|
||||
// =============================================================================
|
||||
// Card List Component (SCSS)
|
||||
// =============================================================================
|
||||
// Универсальные стили для карточек в списках: новости, заявления, документы
|
||||
// Классы feed-* применяются для любых типов контента
|
||||
//
|
||||
// Использование:
|
||||
// - .card — контейнер карточки
|
||||
// - .card-header — шапка карточки (аватар + мета)
|
||||
// - .card-body — тело карточки
|
||||
// - .card-actions — кнопки действий
|
||||
// - .card-list — контейнер потока карточек
|
||||
|
||||
// =============================================================================
|
||||
// ПЕРЕМЕННЫЕ
|
||||
// =============================================================================
|
||||
|
||||
// Карточки
|
||||
$card-icon-size: 42px;
|
||||
$card-icon-size-sm: 36px;
|
||||
$card-icon-size-compact: 32px;
|
||||
$card-icon-border-radius: 9999px;
|
||||
|
||||
// Переходы
|
||||
$card-transition: box-shadow 0.18s ease, transform 0.18s ease, border-color 0.18s ease;
|
||||
$card-action-transition: background 0.2s ease, color 0.2s ease;
|
||||
|
||||
// Тени
|
||||
$card-hover-shadow: 0 4px 16px rgba(0, 0, 0, 0.12);
|
||||
|
||||
// Границы
|
||||
$card-highlighted-border: 3px solid var(--bs-primary);
|
||||
|
||||
// Отступы
|
||||
$feed-title-margin: var(--space-1) 0;
|
||||
|
||||
// =============================================================================
|
||||
// ЗАГОЛОВОК СЕКЦИИ
|
||||
// =============================================================================
|
||||
|
||||
.section-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: var(--gap-component);
|
||||
margin-bottom: var(--space-3);
|
||||
|
||||
.title {
|
||||
font-weight: 800;
|
||||
letter-spacing: 0.2px;
|
||||
margin: 0;
|
||||
}
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// КОНТЕЙНЕР ПОТОКА КАРТОЧЕК
|
||||
// =============================================================================
|
||||
|
||||
.card-list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0;
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// БАЗОВАЯ КАРТОЧКА
|
||||
// =============================================================================
|
||||
|
||||
.card {
|
||||
border: 1px solid color-mix(in srgb, var(--bs-border-color), transparent 10%);
|
||||
border-radius: var(--feed-radius, 18px);
|
||||
background: var(--bs-body-bg);
|
||||
box-shadow: var(--feed-shadow, 0 2px 8px rgba(0, 0, 0, 0.08));
|
||||
transition: $card-transition;
|
||||
overflow: hidden;
|
||||
|
||||
&:hover {
|
||||
box-shadow: $card-hover-shadow;
|
||||
transform: translateY(-1px);
|
||||
}
|
||||
|
||||
// Вариации карточек
|
||||
&.compact {
|
||||
.card-header {
|
||||
padding: var(--space-1) var(--card-padding-sm);
|
||||
}
|
||||
|
||||
.card-body {
|
||||
padding: var(--space-1) var(--card-padding-sm);
|
||||
}
|
||||
|
||||
.card-icon {
|
||||
width: $card-icon-size-compact;
|
||||
height: $card-icon-size-compact;
|
||||
}
|
||||
}
|
||||
|
||||
&.borderless {
|
||||
border: none;
|
||||
box-shadow: none;
|
||||
border-radius: 0;
|
||||
border-bottom: 1px solid var(--bs-border-color);
|
||||
|
||||
&:hover {
|
||||
transform: none;
|
||||
box-shadow: none;
|
||||
background: var(--bs-tertiary-bg);
|
||||
}
|
||||
}
|
||||
|
||||
&.highlighted {
|
||||
border-left: $card-highlighted-border;
|
||||
background: color-mix(in srgb, var(--bs-primary), transparent 95%);
|
||||
}
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// ШАПКА КАРТОЧКИ
|
||||
// =============================================================================
|
||||
|
||||
.card-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: var(--gap-component);
|
||||
padding: var(--card-padding);
|
||||
padding-bottom: var(--space-1);
|
||||
}
|
||||
|
||||
// Аватар
|
||||
.card-icon {
|
||||
width: $card-icon-size;
|
||||
height: $card-icon-size;
|
||||
border-radius: $card-icon-border-radius;
|
||||
overflow: hidden;
|
||||
border: 1px solid color-mix(in srgb, var(--bs-border-color), transparent 20%);
|
||||
background: var(--bs-tertiary-bg);
|
||||
display: grid;
|
||||
place-items: center;
|
||||
flex: 0 0 auto;
|
||||
|
||||
img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: cover;
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
|
||||
// Мета-информация
|
||||
.card-meta {
|
||||
line-height: 1.2;
|
||||
flex: 1;
|
||||
min-width: 0; /* для корректного text-overflow */
|
||||
}
|
||||
|
||||
.card-title {
|
||||
font-weight: 700;
|
||||
color: var(--bs-body-color);
|
||||
display: block;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.card-subtitle {
|
||||
font-size: 0.82rem;
|
||||
color: var(--bs-secondary-color);
|
||||
display: block;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
// Значок закрепления
|
||||
.feed-pin {
|
||||
margin-left: auto;
|
||||
font-size: 0.75rem;
|
||||
padding: var(--space-1);
|
||||
border-radius: 999px;
|
||||
background: #fff3cd;
|
||||
color: #664d03;
|
||||
border: 1px solid #ffe69c;
|
||||
flex-shrink: 0;
|
||||
|
||||
[data-bs-theme="dark"] & {
|
||||
background: #3b2e04;
|
||||
color: #ffda6a;
|
||||
border-color: #4f3f0a;
|
||||
}
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// ТЕЛО КАРТОЧКИ
|
||||
// =============================================================================
|
||||
|
||||
.card-body {
|
||||
padding: var(--space-1) var(--card-padding);
|
||||
padding-bottom: var(--space-1);
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.feed-title {
|
||||
font: 700 1.3rem/1.5 var(--bs-body-font-family);
|
||||
margin: $feed-title-margin;
|
||||
color: #4b4b4b;
|
||||
|
||||
[data-bs-theme="dark"] & {
|
||||
color: var(--bs-body-color);
|
||||
}
|
||||
}
|
||||
|
||||
.feed-text {
|
||||
font-size: 0.95rem;
|
||||
line-height: 1.6;
|
||||
color: var(--bs-body-color);
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// ИЗОБРАЖЕНИЕ В КАРТОЧКЕ
|
||||
// =============================================================================
|
||||
|
||||
.feed-img {
|
||||
position: relative;
|
||||
border-top: 1px solid var(--bs-border-color);
|
||||
background: var(--bs-tertiary-bg);
|
||||
|
||||
img {
|
||||
display: block;
|
||||
width: 100%;
|
||||
height: auto;
|
||||
aspect-ratio: 16/9;
|
||||
object-fit: cover;
|
||||
}
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// ДЕЙСТВИЯ (ЛАЙКИ, КОММЕНТАРИИ)
|
||||
// =============================================================================
|
||||
|
||||
.card-actions {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: var(--space-1);
|
||||
padding: var(--card-padding);
|
||||
padding-top: var(--space-2);
|
||||
border-top: 1px solid var(--bs-border-color);
|
||||
position: relative;
|
||||
z-index: 2;
|
||||
}
|
||||
|
||||
.feed-action {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: var(--space-1);
|
||||
padding: var(--space-1);
|
||||
border-radius: var(--radius-md, 10px);
|
||||
background: transparent;
|
||||
border: none;
|
||||
color: var(--bs-secondary-color);
|
||||
font-size: 0.875rem;
|
||||
cursor: pointer;
|
||||
transition: $card-action-transition;
|
||||
|
||||
&:hover {
|
||||
background: var(--bs-tertiary-bg);
|
||||
color: var(--bs-body-color);
|
||||
}
|
||||
|
||||
&.active {
|
||||
color: var(--bs-primary);
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
i {
|
||||
font-size: 1rem;
|
||||
}
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// ФУТЕР КАРТОЧКИ
|
||||
// =============================================================================
|
||||
|
||||
.feed-footer {
|
||||
padding: var(--space-2) var(--card-padding);
|
||||
border-top: 1px solid var(--bs-border-color);
|
||||
font-size: 0.875rem;
|
||||
color: var(--bs-secondary-color);
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// АДАПТИВНОСТЬ
|
||||
// =============================================================================
|
||||
|
||||
@media (max-width: 576px) {
|
||||
.section-header {
|
||||
gap: var(--space-2);
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.card-header {
|
||||
padding: var(--space-2);
|
||||
padding-bottom: var(--space-1);
|
||||
}
|
||||
|
||||
.card-icon {
|
||||
width: $card-icon-size-sm;
|
||||
height: $card-icon-size-sm;
|
||||
}
|
||||
|
||||
.feed-title {
|
||||
font-size: 1.1rem;
|
||||
}
|
||||
|
||||
.card-actions {
|
||||
padding: var(--space-2);
|
||||
}
|
||||
}
|
||||
341
backend/static/scss_backup_20260115/components/_cards.scss
Normal file
341
backend/static/scss_backup_20260115/components/_cards.scss
Normal file
@ -0,0 +1,341 @@
|
||||
// =============================================================================
|
||||
// Cards Component
|
||||
// =============================================================================
|
||||
// Glassmorphism карточки с поддержкой тёмной темы
|
||||
|
||||
// =============================================================================
|
||||
// 1. БАЗОВЫЕ СТИЛИ КАРТОЧКИ
|
||||
// =============================================================================
|
||||
|
||||
.card {
|
||||
// Применяем glassmorphism эффект
|
||||
@include glassmorphism-card();
|
||||
|
||||
// Прозрачный фон
|
||||
--bs-card-bg: transparent;
|
||||
background: $glass-light-bg !important;
|
||||
|
||||
// Тонкая рамка
|
||||
border: 1px solid $glass-light-border !important;
|
||||
|
||||
// Плавные переходы
|
||||
@include transition-all($transition-medium);
|
||||
overflow: hidden;
|
||||
|
||||
// Hover эффект - усиливаем glassmorphism
|
||||
&:hover {
|
||||
background: rgba(255, 255, 255, 0.15) !important;
|
||||
border-color: rgba(0, 0, 0, 0.12) !important;
|
||||
backdrop-filter: $blur-lg saturate(200%);
|
||||
-webkit-backdrop-filter: $blur-lg saturate(200%);
|
||||
box-shadow: 0 12px 48px 0 rgba(0, 0, 0, 0.15);
|
||||
transform: translateY(-2px);
|
||||
}
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// 2. ТЁМНАЯ ТЕМА
|
||||
// =============================================================================
|
||||
|
||||
[data-bs-theme="dark"] {
|
||||
.card {
|
||||
--bs-card-bg: transparent;
|
||||
--bs-card-border-color: $glass-dark-border;
|
||||
background: transparent !important;
|
||||
border-color: $glass-dark-border !important;
|
||||
|
||||
&:hover {
|
||||
background: rgba(255, 255, 255, 0.03) !important;
|
||||
border-color: rgba(255, 255, 255, 0.15) !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// 3. LAYOUT КОМПОНЕНТЫ КАРТОЧЕК
|
||||
// =============================================================================
|
||||
|
||||
// Контейнер списка карточек
|
||||
.card-list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0;
|
||||
}
|
||||
|
||||
// Заголовок секции
|
||||
.section-header {
|
||||
@include flex-align-center();
|
||||
gap: var(--gap-component, 1rem);
|
||||
margin-bottom: var(--space-3, 0.75rem);
|
||||
|
||||
.title {
|
||||
font-weight: 800;
|
||||
letter-spacing: 0.2px;
|
||||
margin: 0;
|
||||
}
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// 4. СТРУКТУРА КАРТОЧКИ
|
||||
// =============================================================================
|
||||
|
||||
// Card header
|
||||
.card-header {
|
||||
@include flex-align-center();
|
||||
gap: var(--gap-component, 1rem);
|
||||
padding: var(--card-padding, 1rem);
|
||||
padding-bottom: var(--space-1, 0.25rem);
|
||||
background: transparent;
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
// Иконка/аватар в карточке
|
||||
.card-icon {
|
||||
width: 42px;
|
||||
height: 42px;
|
||||
@include circle();
|
||||
overflow: hidden;
|
||||
border: 1px solid color-mix(in srgb, var(--bs-border-color), transparent 20%);
|
||||
background: var(--bs-tertiary-bg);
|
||||
@include flex-center();
|
||||
flex: 0 0 auto;
|
||||
|
||||
img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: cover;
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
|
||||
// Метаинформация
|
||||
.card-meta {
|
||||
line-height: 1.2;
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
// Заголовок карточки
|
||||
.card-title {
|
||||
font-weight: 700;
|
||||
color: var(--bs-body-color);
|
||||
@include text-truncate();
|
||||
display: block;
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
// Подзаголовок
|
||||
.card-subtitle {
|
||||
font-size: 0.82rem;
|
||||
color: var(--bs-secondary-color);
|
||||
@include text-truncate();
|
||||
display: block;
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
// Тело карточки
|
||||
.card-body {
|
||||
padding: var(--space-1, 0.25rem) var(--card-padding, 1rem);
|
||||
padding-bottom: var(--space-1, 0.25rem);
|
||||
position: relative;
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// 5. ДЕЙСТВИЯ КАРТОЧКИ (CARD ACTIONS)
|
||||
// =============================================================================
|
||||
|
||||
.card-actions {
|
||||
@include flex-align-center();
|
||||
gap: var(--space-1, 0.25rem);
|
||||
padding: var(--card-padding, 1rem);
|
||||
padding-top: var(--space-2, 0.5rem);
|
||||
border-top: 1px solid var(--bs-border-color);
|
||||
position: relative;
|
||||
z-index: $z-base;
|
||||
|
||||
// Активные кнопки в actions
|
||||
.btn,
|
||||
button {
|
||||
&.active,
|
||||
&.is-liked {
|
||||
color: var(--bs-primary) !important;
|
||||
font-weight: 600;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// 6. ВАРИАЦИИ КАРТОЧЕК
|
||||
// =============================================================================
|
||||
|
||||
// Компактная карточка
|
||||
.card.compact {
|
||||
.card-header {
|
||||
padding: var(--space-1, 0.25rem) var(--card-padding-sm, 0.5rem);
|
||||
}
|
||||
|
||||
.card-body {
|
||||
padding: var(--space-1, 0.25rem) var(--card-padding-sm, 0.5rem);
|
||||
}
|
||||
|
||||
.card-icon {
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
}
|
||||
}
|
||||
|
||||
// Карточка без рамки
|
||||
.card.borderless {
|
||||
border: none;
|
||||
@include no-shadow();
|
||||
border-radius: 0;
|
||||
border-bottom: 1px solid var(--bs-border-color);
|
||||
|
||||
&:hover {
|
||||
transform: none;
|
||||
@include no-shadow();
|
||||
background: var(--bs-tertiary-bg);
|
||||
}
|
||||
}
|
||||
|
||||
// Выделенная карточка
|
||||
.card.highlighted {
|
||||
border-left: 3px solid var(--bs-primary);
|
||||
background: color-mix(in srgb, var(--bs-primary), transparent 95%);
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// 7. RESPONSIVE
|
||||
// =============================================================================
|
||||
|
||||
@include mobile() {
|
||||
.section-header {
|
||||
gap: var(--space-2, 0.5rem);
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.card-header {
|
||||
padding: var(--space-2, 0.5rem);
|
||||
padding-bottom: var(--space-1, 0.25rem);
|
||||
}
|
||||
|
||||
.card-icon {
|
||||
width: 36px;
|
||||
height: 36px;
|
||||
}
|
||||
|
||||
.card-actions {
|
||||
padding: var(--space-2, 0.5rem);
|
||||
}
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// 8. СТИЛИ КНОПОК (из custom-bootstrap.scss)
|
||||
// =============================================================================
|
||||
|
||||
// Базовые стили для ВСЕХ кнопок - полностью прозрачные
|
||||
.btn {
|
||||
background: transparent !important;
|
||||
border: none !important;
|
||||
color: var(--bs-secondary-color);
|
||||
font-weight: 500;
|
||||
@include transition(color, $transition-fast);
|
||||
|
||||
&:hover:not(:disabled) {
|
||||
color: var(--bs-body-color);
|
||||
}
|
||||
|
||||
&:active:not(:disabled) {
|
||||
color: var(--bs-body-color);
|
||||
}
|
||||
|
||||
&:disabled {
|
||||
opacity: 0.5;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
}
|
||||
|
||||
// Primary кнопка (единственная с цветным фоном)
|
||||
.btn-primary {
|
||||
color: $color-white !important;
|
||||
background: rgba(13, 110, 253, 0.7) !important;
|
||||
|
||||
&:hover:not(:disabled) {
|
||||
background: rgba(13, 110, 253, 0.85) !important;
|
||||
box-shadow: 0 4px 16px rgba(13, 110, 253, 0.3);
|
||||
}
|
||||
|
||||
&:active:not(:disabled) {
|
||||
background: rgba(13, 110, 253, 0.95) !important;
|
||||
}
|
||||
}
|
||||
|
||||
// Secondary, outline, ghost - все прозрачные
|
||||
.btn-secondary,
|
||||
.btn-outline-primary,
|
||||
.btn-outline-secondary,
|
||||
.btn-ghost {
|
||||
background: transparent !important;
|
||||
color: var(--bs-secondary-color);
|
||||
|
||||
&:hover:not(:disabled) {
|
||||
background: transparent !important;
|
||||
color: var(--bs-body-color);
|
||||
}
|
||||
|
||||
&:active:not(:disabled) {
|
||||
background: transparent !important;
|
||||
}
|
||||
}
|
||||
|
||||
// Особый цвет для outline-primary
|
||||
.btn-outline-primary {
|
||||
color: var(--bs-primary);
|
||||
|
||||
&:hover:not(:disabled) {
|
||||
color: var(--bs-primary);
|
||||
background: transparent !important;
|
||||
}
|
||||
}
|
||||
|
||||
// Кнопки в тёмной теме
|
||||
[data-bs-theme="dark"] {
|
||||
.btn {
|
||||
&:hover:not(:disabled),
|
||||
&:active:not(:disabled) {
|
||||
background: transparent !important;
|
||||
}
|
||||
}
|
||||
|
||||
.btn-primary {
|
||||
background: rgba(13, 110, 253, 0.6) !important;
|
||||
|
||||
&:hover:not(:disabled) {
|
||||
background: rgba(13, 110, 253, 0.75) !important;
|
||||
}
|
||||
|
||||
&:active:not(:disabled) {
|
||||
background: rgba(13, 110, 253, 0.85) !important;
|
||||
}
|
||||
}
|
||||
|
||||
.btn-secondary,
|
||||
.btn-outline-primary,
|
||||
.btn-outline-secondary,
|
||||
.btn-ghost {
|
||||
&:hover:not(:disabled),
|
||||
&:active:not(:disabled) {
|
||||
background: transparent !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// 9. УТИЛИТАРНЫЕ КЛАССЫ
|
||||
// =============================================================================
|
||||
|
||||
// Моноширинный шрифт для дат, цифр, кодов
|
||||
.text-mono {
|
||||
font-variant-numeric: tabular-nums;
|
||||
font-family: ui-monospace, 'SF Mono', SFMono-Regular, 'Consolas', 'Liberation Mono', 'Menlo', 'Courier', monospace;
|
||||
}
|
||||
152
backend/static/scss_backup_20260115/components/_comments.scss
Normal file
152
backend/static/scss_backup_20260115/components/_comments.scss
Normal file
@ -0,0 +1,152 @@
|
||||
// =============================================================================
|
||||
// Comments Component
|
||||
// =============================================================================
|
||||
// Единый стиль для комментариев во всех местах приложения:
|
||||
// - В карточках постов (_feed_cards.html)
|
||||
// - На странице деталей поста (post_detail.html)
|
||||
// - В модальном окне (postDetailModal.js)
|
||||
|
||||
.comment-item {
|
||||
padding: 0.5rem 0; // py-2
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
|
||||
.comment-header {
|
||||
font-size: 0.875rem; // small
|
||||
margin-bottom: 0.25rem;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex-wrap: wrap;
|
||||
|
||||
.comment-author {
|
||||
font-weight: 600; // fw-semibold
|
||||
text-decoration: none;
|
||||
color: inherit;
|
||||
|
||||
&:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
}
|
||||
|
||||
.comment-separator {
|
||||
color: var(--bs-secondary);
|
||||
margin: 0 0.25rem;
|
||||
}
|
||||
|
||||
.comment-date {
|
||||
color: var(--bs-secondary);
|
||||
}
|
||||
|
||||
.badge {
|
||||
font-size: 0.7rem;
|
||||
padding: 0.15rem 0.4rem;
|
||||
}
|
||||
}
|
||||
|
||||
.comment-body {
|
||||
font-size: 0.875rem; // small
|
||||
line-height: 1.5;
|
||||
white-space: pre-wrap;
|
||||
word-wrap: break-word;
|
||||
background: var(--bs-tertiary-bg);
|
||||
padding: 0.5rem 0.75rem;
|
||||
border-radius: 12px;
|
||||
max-width: 80%;
|
||||
}
|
||||
|
||||
// Комментарии автора поста справа
|
||||
&.comment-item--author {
|
||||
align-items: flex-end;
|
||||
|
||||
.comment-header {
|
||||
flex-direction: row-reverse;
|
||||
|
||||
.comment-separator {
|
||||
order: 0;
|
||||
}
|
||||
|
||||
.comment-date {
|
||||
order: -1;
|
||||
}
|
||||
}
|
||||
|
||||
.comment-body {
|
||||
background: var(--bs-primary-bg-subtle, rgba(13, 110, 253, 0.1));
|
||||
border: 1px solid var(--bs-primary-border-subtle, rgba(13, 110, 253, 0.2));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Контейнер для списка комментариев
|
||||
.comments-list {
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
// Заголовок секции комментариев
|
||||
.comments-section-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-bottom: 0.5rem;
|
||||
|
||||
.comments-icon {
|
||||
font-size: 1.5rem;
|
||||
color: var(--bs-primary);
|
||||
margin-right: 0.5rem;
|
||||
}
|
||||
|
||||
.comments-title {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.comments-count {
|
||||
font-size: 0.875rem;
|
||||
color: var(--bs-secondary);
|
||||
margin-left: 0.5rem;
|
||||
}
|
||||
}
|
||||
|
||||
// Обёртка для комментариев с sticky формой
|
||||
.comments-section-wrapper {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
// Скроллируемая область с комментариями
|
||||
.comments-scrollable {
|
||||
max-height: 400px;
|
||||
overflow-y: auto;
|
||||
overflow-x: hidden;
|
||||
padding-bottom: 1rem;
|
||||
|
||||
// Firefox скроллбар
|
||||
scrollbar-width: thin;
|
||||
scrollbar-color: rgba(0, 0, 0, 0.15) transparent;
|
||||
|
||||
// Стилизация скроллбара (Chrome, Safari, Edge)
|
||||
&::-webkit-scrollbar {
|
||||
width: 8px;
|
||||
}
|
||||
|
||||
&::-webkit-scrollbar-track {
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
&::-webkit-scrollbar-thumb {
|
||||
background: rgba(0, 0, 0, 0.15);
|
||||
border-radius: 4px;
|
||||
|
||||
&:hover {
|
||||
background: rgba(0, 0, 0, 0.25);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Sticky форма для комментариев
|
||||
.comment-form-sticky {
|
||||
position: sticky;
|
||||
bottom: 0;
|
||||
background: var(--bs-body-bg);
|
||||
z-index: 10;
|
||||
border-top: 1px solid var(--bs-border-color);
|
||||
padding-top: 1rem;
|
||||
}
|
||||
327
backend/static/scss_backup_20260115/components/_common.scss
Normal file
327
backend/static/scss_backup_20260115/components/_common.scss
Normal file
@ -0,0 +1,327 @@
|
||||
/**
|
||||
* _common.scss
|
||||
* Общие переиспользуемые классы для всех компонентов
|
||||
* Избегает дублирования между компонентами
|
||||
*/
|
||||
|
||||
/* ─────────────────────────────────────────────────────────────
|
||||
BUTTONS
|
||||
────────────────────────────────────────────────────────────── */
|
||||
|
||||
/* Иконочная кнопка */
|
||||
.btn-icon {
|
||||
width: 36px;
|
||||
height: 36px;
|
||||
display: inline-grid;
|
||||
place-items: center;
|
||||
border: 1px solid var(--bs-border-color);
|
||||
border-radius: var(--button-radius, 10px);
|
||||
background: transparent;
|
||||
color: var(--bs-body-color);
|
||||
cursor: pointer;
|
||||
transition: all var(--transition-fast, 0.15s);
|
||||
|
||||
&:hover {
|
||||
background: var(--bs-tertiary-bg);
|
||||
border-color: var(--bs-border-color);
|
||||
}
|
||||
|
||||
&--primary {
|
||||
color: var(--bs-primary);
|
||||
}
|
||||
|
||||
&--danger {
|
||||
color: var(--bs-danger);
|
||||
}
|
||||
|
||||
&--success {
|
||||
color: var(--bs-success);
|
||||
}
|
||||
|
||||
&:disabled {
|
||||
opacity: 0.5;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
}
|
||||
|
||||
/* ─────────────────────────────────────────────────────────────
|
||||
BADGES
|
||||
────────────────────────────────────────────────────────────── */
|
||||
|
||||
/* Бейдж подтверждения */
|
||||
.badge-acked {
|
||||
background: color-mix(in srgb, var(--bs-success) 20%, transparent);
|
||||
color: var(--bs-success);
|
||||
border: 1px solid color-mix(in srgb, var(--bs-success) 30%, transparent);
|
||||
padding: 0.28rem 0.55rem;
|
||||
border-radius: var(--badge-radius, 999px);
|
||||
font-size: 0.8rem;
|
||||
font-weight: 500;
|
||||
|
||||
[data-bs-theme="dark"] & {
|
||||
background: color-mix(in srgb, var(--bs-success) 15%, #000);
|
||||
color: color-mix(in srgb, var(--bs-success) 80%, #fff);
|
||||
border-color: color-mix(in srgb, var(--bs-success) 25%, transparent);
|
||||
}
|
||||
}
|
||||
|
||||
/* Бейдж статуса */
|
||||
.badge-status {
|
||||
padding: var(--space-1);
|
||||
border-radius: var(--badge-radius, 999px);
|
||||
font-size: 0.75rem;
|
||||
font-weight: 600;
|
||||
border: 1px solid;
|
||||
|
||||
&--pending {
|
||||
background: color-mix(in srgb, var(--bs-warning) 15%, transparent);
|
||||
color: var(--bs-warning);
|
||||
border-color: color-mix(in srgb, var(--bs-warning) 30%, transparent);
|
||||
}
|
||||
|
||||
&--approved {
|
||||
background: color-mix(in srgb, var(--bs-success) 15%, transparent);
|
||||
color: var(--bs-success);
|
||||
border-color: color-mix(in srgb, var(--bs-success) 30%, transparent);
|
||||
}
|
||||
|
||||
&--rejected {
|
||||
background: color-mix(in srgb, var(--bs-danger) 15%, transparent);
|
||||
color: var(--bs-danger);
|
||||
border-color: color-mix(in srgb, var(--bs-danger) 30%, transparent);
|
||||
}
|
||||
}
|
||||
|
||||
/* ─────────────────────────────────────────────────────────────
|
||||
PINNED BADGE (централизованный)
|
||||
────────────────────────────────────────────────────────────── */
|
||||
.chat-pinned-badge {
|
||||
position: absolute;
|
||||
background: var(--bs-warning);
|
||||
color: white;
|
||||
@include circle();
|
||||
@include flex-center();
|
||||
z-index: 1;
|
||||
|
||||
// Размер по умолчанию (средний)
|
||||
width: 22px;
|
||||
height: 22px;
|
||||
top: 6px;
|
||||
right: 6px;
|
||||
font-size: 0.75rem;
|
||||
|
||||
// Маленький вариант (для списка чатов)
|
||||
&--sm {
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
top: 4px;
|
||||
right: 4px;
|
||||
font-size: 0.7rem;
|
||||
}
|
||||
|
||||
// Большой вариант (для детального чата)
|
||||
&--lg {
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
top: 8px;
|
||||
right: 8px;
|
||||
font-size: 0.8rem;
|
||||
}
|
||||
}
|
||||
|
||||
/* ─────────────────────────────────────────────────────────────
|
||||
AVATARS
|
||||
────────────────────────────────────────────────────────────── */
|
||||
|
||||
.avatar {
|
||||
border-radius: 50%;
|
||||
overflow: hidden;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background: var(--bs-tertiary-bg);
|
||||
border: 1px solid color-mix(in srgb, var(--bs-border-color), transparent 20%);
|
||||
flex-shrink: 0;
|
||||
|
||||
img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: cover;
|
||||
display: block;
|
||||
}
|
||||
|
||||
i {
|
||||
color: var(--bs-secondary-color);
|
||||
}
|
||||
}
|
||||
|
||||
/* Размеры */
|
||||
.avatar-xs {
|
||||
width: var(--avatar-xs, 28px);
|
||||
height: var(--avatar-xs, 28px);
|
||||
}
|
||||
|
||||
.avatar-sm {
|
||||
width: var(--avatar-sm, 36px);
|
||||
height: var(--avatar-sm, 36px);
|
||||
}
|
||||
|
||||
.avatar-md {
|
||||
width: var(--avatar-md, 44px);
|
||||
height: var(--avatar-md, 44px);
|
||||
}
|
||||
|
||||
.avatar-lg {
|
||||
width: var(--avatar-lg, 64px);
|
||||
height: var(--avatar-lg, 64px);
|
||||
}
|
||||
|
||||
.avatar-xl {
|
||||
width: var(--avatar-xl, 96px);
|
||||
height: var(--avatar-xl, 96px);
|
||||
}
|
||||
|
||||
/* ─────────────────────────────────────────────────────────────
|
||||
FEED AUTHOR & META
|
||||
────────────────────────────────────────────────────────────── */
|
||||
|
||||
.card-title {
|
||||
font-weight: 700;
|
||||
color: var(--bs-body-color);
|
||||
display: block;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.card-subtitle {
|
||||
font-size: 0.82rem;
|
||||
color: var(--bs-secondary-color);
|
||||
display: block;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
|
||||
a {
|
||||
color: inherit;
|
||||
text-decoration: none;
|
||||
|
||||
&:hover {
|
||||
color: var(--bs-primary);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* ─────────────────────────────────────────────────────────────
|
||||
AVATARS (Centralized)
|
||||
────────────────────────────────────────────────────────────── */
|
||||
|
||||
.chat-avatar,
|
||||
.avatar {
|
||||
@include circle();
|
||||
overflow: hidden;
|
||||
flex-shrink: 0;
|
||||
background: var(--bs-tertiary-bg);
|
||||
border: 1px solid var(--bs-border-color);
|
||||
|
||||
img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: cover;
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
|
||||
// Size variants
|
||||
.chat-avatar,
|
||||
.avatar {
|
||||
width: $avatar-md;
|
||||
height: $avatar-md;
|
||||
}
|
||||
|
||||
.chat-avatar--sm,
|
||||
.avatar--sm {
|
||||
width: $avatar-sm;
|
||||
height: $avatar-sm;
|
||||
}
|
||||
|
||||
.chat-avatar--lg,
|
||||
.avatar--lg {
|
||||
width: $avatar-lg;
|
||||
height: $avatar-lg;
|
||||
}
|
||||
|
||||
.chat-avatar--xl,
|
||||
.avatar--xl {
|
||||
width: $avatar-xl;
|
||||
height: $avatar-xl;
|
||||
}
|
||||
|
||||
/* ─────────────────────────────────────────────────────────────
|
||||
CHIPS
|
||||
────────────────────────────────────────────────────────────── */
|
||||
|
||||
.chip {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: var(--space-1);
|
||||
padding: var(--space-1);
|
||||
border-radius: var(--badge-radius, 999px);
|
||||
background: var(--bs-tertiary-bg);
|
||||
border: 1px solid var(--bs-border-color);
|
||||
font-size: 0.82rem;
|
||||
color: var(--bs-body-color);
|
||||
}
|
||||
|
||||
.chip-remove {
|
||||
cursor: pointer;
|
||||
color: var(--bs-danger);
|
||||
font-weight: 700;
|
||||
line-height: 1;
|
||||
|
||||
&:hover {
|
||||
color: color-mix(in srgb, var(--bs-danger) 80%, #000);
|
||||
}
|
||||
}
|
||||
|
||||
/* ─────────────────────────────────────────────────────────────
|
||||
UTILITIES
|
||||
────────────────────────────────────────────────────────────── */
|
||||
|
||||
/* Текст с многоточием */
|
||||
.text-truncate-2 {
|
||||
display: -webkit-box;
|
||||
-webkit-line-clamp: 2;
|
||||
-webkit-box-orient: vertical;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.text-truncate-3 {
|
||||
display: -webkit-box;
|
||||
-webkit-line-clamp: 3;
|
||||
-webkit-box-orient: vertical;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
/* Кастомный scrollbar */
|
||||
.custom-scrollbar {
|
||||
&::-webkit-scrollbar {
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
}
|
||||
|
||||
&::-webkit-scrollbar-track {
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
&::-webkit-scrollbar-thumb {
|
||||
background: color-mix(in srgb, var(--bs-border-color) 70%, transparent);
|
||||
border-radius: 10px;
|
||||
|
||||
&:hover {
|
||||
background: var(--bs-border-color);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,26 @@
|
||||
// =============================================================================
|
||||
// Department Controls (SCSS)
|
||||
// =============================================================================
|
||||
// Стили для модального управления отделом
|
||||
|
||||
// =============================================================================
|
||||
// MODAL Z-INDEX OVERRIDES
|
||||
// =============================================================================
|
||||
|
||||
// Модалки поверх всего
|
||||
.modal-backdrop.show {
|
||||
z-index: 3000 !important;
|
||||
}
|
||||
|
||||
.modal.show {
|
||||
z-index: 3010 !important;
|
||||
}
|
||||
|
||||
.modal-dialog {
|
||||
pointer-events: auto !important;
|
||||
}
|
||||
|
||||
// Портальный dropdown автокомплита ролей
|
||||
.role-portal {
|
||||
z-index: 1800 !important;
|
||||
}
|
||||
@ -0,0 +1,248 @@
|
||||
// =============================================================================
|
||||
// Document List Styles (SCSS)
|
||||
// =============================================================================
|
||||
// Стили списка документов, recipient picker, и подтверждений
|
||||
|
||||
// =============================================================================
|
||||
// ПЕРЕМЕННЫЕ
|
||||
// =============================================================================
|
||||
|
||||
$doc-icon-size: 28px;
|
||||
$doc-row-padding: var(--space-2);
|
||||
$doc-border-unacked-width: 3px;
|
||||
$recipient-avatar-size: 26px;
|
||||
$card-icon-size: 32px;
|
||||
|
||||
// Цвета
|
||||
$doc-hover-primary: 4%;
|
||||
$doc-unacked-bg: 8%;
|
||||
$doc-unacked-hover: 15%;
|
||||
|
||||
// =============================================================================
|
||||
// СПИСОК ДОКУМЕНТОВ
|
||||
// =============================================================================
|
||||
|
||||
#docList.card-list {
|
||||
display: block;
|
||||
gap: 0 !important;
|
||||
}
|
||||
|
||||
#docList .doc-row {
|
||||
padding: $doc-row-padding !important;
|
||||
margin: 0 !important;
|
||||
border-bottom: 1px solid var(--bs-border-color);
|
||||
transition: background-color 0.2s ease;
|
||||
cursor: pointer;
|
||||
|
||||
&:hover {
|
||||
background-color: color-mix(in srgb, var(--bs-primary) $doc-hover-primary, transparent);
|
||||
}
|
||||
|
||||
&.doc-unacked {
|
||||
background-color: color-mix(in srgb, var(--bs-warning) $doc-unacked-bg, transparent);
|
||||
border-left: $doc-border-unacked-width solid var(--bs-warning);
|
||||
padding-left: calc($doc-row-padding - $doc-border-unacked-width) !important;
|
||||
|
||||
&:hover {
|
||||
background-color: color-mix(in srgb, var(--bs-warning) $doc-unacked-hover, transparent);
|
||||
}
|
||||
}
|
||||
|
||||
&:last-child {
|
||||
border-bottom: 0;
|
||||
}
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// ЗАГОЛОВОК ДОКУМЕНТА
|
||||
// =============================================================================
|
||||
|
||||
#docList .card-header {
|
||||
display: grid;
|
||||
grid-template-columns: $doc-icon-size 1fr auto;
|
||||
align-items: center;
|
||||
gap: 0.75rem;
|
||||
margin: 0;
|
||||
padding: 0 !important;
|
||||
}
|
||||
|
||||
#docList .feed-ico {
|
||||
width: $doc-icon-size;
|
||||
height: $doc-icon-size;
|
||||
display: grid;
|
||||
place-items: center;
|
||||
border-radius: 8px;
|
||||
background: color-mix(in srgb, var(--bs-body-bg) 90%, transparent);
|
||||
border: 1px solid color-mix(in srgb, var(--bs-border-color), transparent 30%);
|
||||
color: var(--bs-primary);
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// ДЕЙСТВИЯ С ДОКУМЕНТАМИ
|
||||
// =============================================================================
|
||||
|
||||
.doc-actions {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.4rem;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// SCOPE SWITCH
|
||||
// =============================================================================
|
||||
|
||||
.scope-switch .btn {
|
||||
min-height: 36px;
|
||||
border-radius: 999px;
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// АДАПТИВНОСТЬ: МОБИЛЬНЫЕ
|
||||
// =============================================================================
|
||||
|
||||
@media (max-width: 576px) {
|
||||
#docList .card-header {
|
||||
grid-template-columns: $doc-icon-size 1fr;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
|
||||
.doc-actions {
|
||||
grid-column: 2;
|
||||
margin-top: 0.5rem;
|
||||
justify-content: flex-start;
|
||||
|
||||
.btn,
|
||||
.btn-icon {
|
||||
font-size: 0.85rem;
|
||||
padding: 0.35rem 0.65rem;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// RECIPIENT PICKER
|
||||
// =============================================================================
|
||||
|
||||
.recipient-picker {
|
||||
border: 1px solid var(--bs-border-color);
|
||||
border-radius: 12px;
|
||||
padding: var(--space-2);
|
||||
background: var(--bs-body-bg);
|
||||
}
|
||||
|
||||
.recipient-picker .rp-selected {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: var(--space-1);
|
||||
margin-bottom: var(--space-1);
|
||||
}
|
||||
|
||||
.recipient-picker .rp-chip {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: var(--space-1);
|
||||
border: 1px solid var(--bs-border-color);
|
||||
border-radius: 999px;
|
||||
padding: var(--space-1);
|
||||
font-size: 0.85rem;
|
||||
|
||||
button {
|
||||
border: 0;
|
||||
background: transparent;
|
||||
padding: 0 var(--space-1);
|
||||
line-height: 1;
|
||||
}
|
||||
}
|
||||
|
||||
.recipient-picker .rp-search {
|
||||
position: relative;
|
||||
|
||||
input {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
.recipient-picker .rp-results {
|
||||
margin-top: 0.5rem;
|
||||
max-height: 220px;
|
||||
overflow: auto;
|
||||
border: 1px solid var(--bs-border-color);
|
||||
border-radius: 10px;
|
||||
}
|
||||
|
||||
.recipient-picker .rp-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: var(--space-1);
|
||||
padding: var(--space-1);
|
||||
border-bottom: 1px solid var(--bs-border-color);
|
||||
|
||||
&:last-child {
|
||||
border-bottom: 0;
|
||||
}
|
||||
|
||||
small {
|
||||
color: var(--bs-secondary-color);
|
||||
}
|
||||
}
|
||||
|
||||
.recipient-picker .rp-empty {
|
||||
padding: var(--space-1);
|
||||
text-align: center;
|
||||
color: var(--bs-secondary-color);
|
||||
}
|
||||
|
||||
.recipient-picker .rp-avatar {
|
||||
width: $recipient-avatar-size;
|
||||
height: $recipient-avatar-size;
|
||||
border-radius: 50%;
|
||||
background: color-mix(in srgb, var(--bs-primary) 12%, transparent);
|
||||
display: inline-grid;
|
||||
place-items: center;
|
||||
font-size: 0.7rem;
|
||||
}
|
||||
|
||||
.recipient-block[hidden] {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// ACKNOWLEDGMENTS (ПОДТВЕРЖДЕНИЯ)
|
||||
// =============================================================================
|
||||
|
||||
.acks-list {
|
||||
max-height: 50vh;
|
||||
overflow: auto;
|
||||
margin: 0;
|
||||
padding-left: 1.1rem;
|
||||
}
|
||||
|
||||
.acks-empty {
|
||||
color: var(--bs-secondary-color);
|
||||
}
|
||||
|
||||
.acks-more {
|
||||
margin-top: 0.5rem;
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// ИКОНКИ
|
||||
// =============================================================================
|
||||
|
||||
.card-icon {
|
||||
width: $card-icon-size;
|
||||
height: $card-icon-size;
|
||||
flex: 0 0 $card-icon-size;
|
||||
border-radius: 50%;
|
||||
overflow: hidden;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.rp-avatar {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: cover;
|
||||
}
|
||||
@ -0,0 +1,134 @@
|
||||
// =============================================================================
|
||||
// Feed-Specific Styles (SCSS)
|
||||
// =============================================================================
|
||||
// Стили, специфичные для постов в ленте новостей
|
||||
|
||||
// =============================================================================
|
||||
// ПЕРЕМЕННЫЕ
|
||||
// =============================================================================
|
||||
|
||||
$feed-pin-bg-light: #fff3cd;
|
||||
$feed-pin-color-light: #664d03;
|
||||
$feed-pin-border-light: #ffe69c;
|
||||
$feed-pin-bg-dark: #3b2e04;
|
||||
$feed-pin-color-dark: #ffda6a;
|
||||
$feed-pin-border-dark: #4f3f0a;
|
||||
|
||||
$feed-title-color-light: #4b4b4b;
|
||||
|
||||
// =============================================================================
|
||||
// ЗНАЧОК ЗАКРЕПЛЕНИЯ
|
||||
// =============================================================================
|
||||
|
||||
.feed-pin {
|
||||
margin-left: auto;
|
||||
font-size: 0.75rem;
|
||||
padding: var(--space-1, 0.25rem);
|
||||
border-radius: 999px;
|
||||
background: $feed-pin-bg-light;
|
||||
color: $feed-pin-color-light;
|
||||
border: 1px solid $feed-pin-border-light;
|
||||
flex-shrink: 0;
|
||||
|
||||
[data-bs-theme="dark"] & {
|
||||
background: $feed-pin-bg-dark;
|
||||
color: $feed-pin-color-dark;
|
||||
border-color: $feed-pin-border-dark;
|
||||
}
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// ЗАГОЛОВОК ПОСТА
|
||||
// =============================================================================
|
||||
|
||||
.feed-title {
|
||||
font: 700 1.3rem/1.5 var(--bs-body-font-family);
|
||||
margin: var(--space-1, 0.25rem) 0;
|
||||
color: $feed-title-color-light;
|
||||
|
||||
[data-bs-theme="dark"] & {
|
||||
color: var(--bs-body-color);
|
||||
}
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// ТЕКСТ ПОСТА
|
||||
// =============================================================================
|
||||
|
||||
.feed-text {
|
||||
font-size: 0.95rem;
|
||||
line-height: 1.6;
|
||||
color: var(--bs-body-color);
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// ИЗОБРАЖЕНИЕ ПОСТА
|
||||
// =============================================================================
|
||||
|
||||
.feed-img {
|
||||
position: relative;
|
||||
border-top: 1px solid var(--bs-border-color);
|
||||
background: var(--bs-tertiary-bg);
|
||||
|
||||
img {
|
||||
display: block;
|
||||
width: 100%;
|
||||
height: auto;
|
||||
aspect-ratio: 16/9;
|
||||
object-fit: cover;
|
||||
}
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// КНОПКИ ДЕЙСТВИЙ (ЛАЙКИ, КОММЕНТАРИИ)
|
||||
// =============================================================================
|
||||
|
||||
.feed-action {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: var(--space-1, 0.25rem);
|
||||
padding: var(--space-1, 0.25rem);
|
||||
border-radius: var(--radius-md, 10px);
|
||||
background: transparent;
|
||||
border: none;
|
||||
color: var(--bs-secondary-color);
|
||||
font-size: 0.875rem;
|
||||
cursor: pointer;
|
||||
transition: background 0.2s ease, color 0.2s ease;
|
||||
|
||||
&:hover {
|
||||
background: var(--bs-tertiary-bg);
|
||||
color: var(--bs-body-color);
|
||||
}
|
||||
|
||||
&.active {
|
||||
color: var(--bs-primary);
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
i {
|
||||
font-size: 1rem;
|
||||
}
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// ФУТЕР ПОСТА
|
||||
// =============================================================================
|
||||
|
||||
.feed-footer {
|
||||
padding: var(--space-2, 0.5rem) var(--card-padding, 1rem);
|
||||
border-top: 1px solid var(--bs-border-color);
|
||||
font-size: 0.875rem;
|
||||
color: var(--bs-secondary-color);
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// АДАПТИВНОСТЬ
|
||||
// =============================================================================
|
||||
|
||||
@media (max-width: 576px) {
|
||||
.feed-title {
|
||||
font-size: 1.1rem;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,546 @@
|
||||
// =============================================================================
|
||||
// iOS-style Components (SCSS)
|
||||
// =============================================================================
|
||||
// iOS-style компоненты: карточки, списки, аккордеоны, badges
|
||||
//
|
||||
// Зависимости:
|
||||
// - variables.css (--feed-radius, --feed-shadow, --navbar-h)
|
||||
// - common.css (.chip определён там)
|
||||
|
||||
// =============================================================================
|
||||
// ПЕРЕМЕННЫЕ
|
||||
// =============================================================================
|
||||
|
||||
// Карточки
|
||||
$ios-card-border-radius: var(--feed-radius);
|
||||
$ios-card-transition: box-shadow 0.18s ease, transform 0.18s ease, border-color 0.18s ease;
|
||||
$ios-card-hover-transform: translateY(-1px);
|
||||
|
||||
// Аватар
|
||||
$avatar-size: 132px;
|
||||
$avatar-border-radius: 28px;
|
||||
$avatar-font-size: 3rem;
|
||||
|
||||
// Списки
|
||||
$ios-list-border-radius: 14px;
|
||||
$ios-cell-gap: 0.75rem;
|
||||
$ios-cell-padding: 0.85rem 1rem;
|
||||
|
||||
// Badges
|
||||
$badge-border-radius: 999px;
|
||||
$badge-font-size: 0.8rem;
|
||||
$badge-icon-size: 0.95rem;
|
||||
|
||||
// Аккордеон
|
||||
$ios-acc-border-radius: 14px;
|
||||
|
||||
// Collapse
|
||||
$collapse-transition: height 0.35s ease;
|
||||
$collapse-caret-transition: transform 0.25s ease;
|
||||
|
||||
// Контейнер
|
||||
$container-xxl-max-width: 1820px;
|
||||
|
||||
// =============================================================================
|
||||
// МИКСИНЫ
|
||||
// =============================================================================
|
||||
|
||||
// Миксин для цветовых badge-ов
|
||||
@mixin badge-variant($color) {
|
||||
background: color-mix(in srgb, var(--bs-#{$color}) 15%, var(--bs-body-bg));
|
||||
color: color-mix(in srgb, var(--bs-#{$color}) 60%, black 0%);
|
||||
border: 1px solid color-mix(in srgb, var(--bs-#{$color}) 35%, transparent);
|
||||
|
||||
&:hover {
|
||||
background: color-mix(in srgb, var(--bs-#{$color}) 20%, var(--bs-body-bg));
|
||||
}
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// BASE CARD
|
||||
// =============================================================================
|
||||
|
||||
.ios-card {
|
||||
border: 1px solid color-mix(in srgb, var(--bs-border-color), transparent 10%);
|
||||
border-radius: $ios-card-border-radius;
|
||||
background: var(--bs-body-bg);
|
||||
box-shadow: var(--feed-shadow);
|
||||
transition: $ios-card-transition;
|
||||
overflow: hidden;
|
||||
|
||||
&:hover {
|
||||
box-shadow: var(--feed-shadow-hover);
|
||||
transform: $ios-card-hover-transform;
|
||||
}
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// HEADER
|
||||
// =============================================================================
|
||||
|
||||
.ios-header {
|
||||
position: sticky;
|
||||
top: var(--navbar-h, 0px);
|
||||
z-index: 5;
|
||||
padding: var(--space-2) 0;
|
||||
backdrop-filter: saturate(180%) blur(12px);
|
||||
background: color-mix(in srgb, var(--bs-body-bg) 85%, transparent);
|
||||
border-bottom: none !important;
|
||||
}
|
||||
|
||||
.ios-title {
|
||||
font-weight: 800;
|
||||
letter-spacing: -0.01em;
|
||||
}
|
||||
|
||||
.row.g-ios {
|
||||
align-items: flex-start;
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// AVATAR
|
||||
// =============================================================================
|
||||
|
||||
.ios-ava,
|
||||
.ios-ava--ph {
|
||||
display: grid;
|
||||
place-items: center;
|
||||
background: var(--bs-tertiary-bg);
|
||||
color: var(--bs-secondary-color);
|
||||
font-size: $avatar-font-size;
|
||||
line-height: 1;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
border-radius: $avatar-border-radius;
|
||||
border: 1px solid color-mix(in srgb, var(--bs-border-color), transparent 20%);
|
||||
}
|
||||
|
||||
.ava-wrap {
|
||||
position: relative;
|
||||
width: $avatar-size;
|
||||
height: $avatar-size;
|
||||
margin: 0 auto var(--space-2);
|
||||
display: block;
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// NAME & ROLE
|
||||
// =============================================================================
|
||||
|
||||
.ios-name {
|
||||
font-weight: 800;
|
||||
font-size: 1.25rem;
|
||||
}
|
||||
|
||||
.ios-role {
|
||||
font-size: 0.9rem;
|
||||
color: var(--bs-secondary-color);
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// CHIPS
|
||||
// =============================================================================
|
||||
|
||||
// Base .chip определён в common.css
|
||||
.chips {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: var(--space-1);
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// COLLAPSE TRIGGERS
|
||||
// =============================================================================
|
||||
|
||||
.ios-collapse-trigger {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: var(--space-2);
|
||||
padding: var(--space-1) var(--space-2);
|
||||
cursor: pointer;
|
||||
border-radius: $ios-list-border-radius;
|
||||
border: 1px solid color-mix(in srgb, var(--bs-border-color), transparent 20%);
|
||||
background: color-mix(in srgb, var(--bs-body-bg) 94%, transparent);
|
||||
color: var(--bs-body-color);
|
||||
}
|
||||
|
||||
.ios-collapse-caret {
|
||||
transition: $collapse-caret-transition;
|
||||
}
|
||||
|
||||
.ios-collapse-trigger[aria-expanded="true"] .ios-collapse-caret {
|
||||
transform: rotate(180deg);
|
||||
}
|
||||
|
||||
.ios-collapse-trigger + .collapse {
|
||||
margin-top: 0 !important;
|
||||
|
||||
> .ios-list,
|
||||
> .chips {
|
||||
margin-top: 0.5rem;
|
||||
}
|
||||
}
|
||||
|
||||
.collapsing {
|
||||
overflow: hidden;
|
||||
transition: $collapse-transition;
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// LISTS
|
||||
// =============================================================================
|
||||
|
||||
.ios-list {
|
||||
border-radius: $ios-list-border-radius;
|
||||
overflow: hidden;
|
||||
background: var(--bs-body-bg);
|
||||
border: 1px solid color-mix(in srgb, var(--bs-border-color), transparent 10%);
|
||||
}
|
||||
|
||||
.ios-cell {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: $ios-cell-gap;
|
||||
padding: $ios-cell-padding;
|
||||
border-bottom: 1px solid color-mix(in srgb, var(--bs-border-color), transparent 10%);
|
||||
flex-wrap: wrap;
|
||||
|
||||
&:last-child {
|
||||
border-bottom: 0;
|
||||
}
|
||||
|
||||
i {
|
||||
width: 1.25rem;
|
||||
text-align: center;
|
||||
opacity: 0.9;
|
||||
}
|
||||
|
||||
a {
|
||||
text-decoration: none;
|
||||
color: inherit;
|
||||
}
|
||||
|
||||
// Departments
|
||||
&.dept-item {
|
||||
justify-content: space-between;
|
||||
|
||||
> .d-flex {
|
||||
flex: 0 1 auto;
|
||||
flex-wrap: wrap;
|
||||
justify-content: flex-end;
|
||||
gap: 0.35rem 0.5rem;
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
&.is-editing .dept-role {
|
||||
display: none !important;
|
||||
}
|
||||
}
|
||||
|
||||
&.dept-head {
|
||||
border-left: 0;
|
||||
background: transparent;
|
||||
}
|
||||
}
|
||||
|
||||
.ios-section-title {
|
||||
font-weight: 700;
|
||||
font-size: 1rem;
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
.rounded-ios {
|
||||
border-radius: $ios-list-border-radius !important;
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// DEPARTMENTS LIST
|
||||
// =============================================================================
|
||||
|
||||
.dept-line {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.dept-name {
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.dept-role {
|
||||
color: var(--bs-secondary-color);
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// BADGES (HEAD & GEAR)
|
||||
// =============================================================================
|
||||
|
||||
.badge-head,
|
||||
.badge-gear {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: var(--space-1);
|
||||
padding: var(--space-1);
|
||||
border-radius: $badge-border-radius;
|
||||
font-weight: 700;
|
||||
font-size: $badge-font-size;
|
||||
line-height: 1;
|
||||
white-space: nowrap;
|
||||
flex: 0 0 auto;
|
||||
|
||||
.bi {
|
||||
font-size: $badge-icon-size;
|
||||
line-height: 1;
|
||||
}
|
||||
}
|
||||
|
||||
.badge-head {
|
||||
@include badge-variant(warning);
|
||||
}
|
||||
|
||||
.badge-gear {
|
||||
@include badge-variant(primary);
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
// Скрытие текста на мобильных
|
||||
@media (max-width: 420px) {
|
||||
.badge-head .lbl,
|
||||
.badge-gear .lbl {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 576px) {
|
||||
.badge-head .lbl,
|
||||
.badge-gear .lbl {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// ACCORDION (IOS-ACC)
|
||||
// =============================================================================
|
||||
|
||||
.ios-acc {
|
||||
border: 1px solid color-mix(in srgb, var(--bs-border-color), transparent 20%);
|
||||
border-radius: $ios-acc-border-radius;
|
||||
background: var(--bs-body-bg);
|
||||
overflow: hidden;
|
||||
|
||||
& + & {
|
||||
margin-top: var(--space-2);
|
||||
}
|
||||
|
||||
.ios-collapse-trigger {
|
||||
border: 0;
|
||||
border-radius: 0;
|
||||
background: transparent;
|
||||
width: 100%;
|
||||
padding: var(--space-2);
|
||||
|
||||
&:focus {
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
& + .collapse {
|
||||
margin-top: 0 !important;
|
||||
}
|
||||
}
|
||||
|
||||
.collapse {
|
||||
margin: 0 !important;
|
||||
}
|
||||
|
||||
.ios-list {
|
||||
border: 0;
|
||||
border-radius: 0;
|
||||
}
|
||||
|
||||
.chips {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.ios-acc-body {
|
||||
padding: var(--space-2);
|
||||
border-top: 1px solid color-mix(in srgb, var(--bs-border-color), transparent 20%);
|
||||
}
|
||||
|
||||
.ios-collapse-trigger + .collapse {
|
||||
margin-top: 0 !important;
|
||||
}
|
||||
|
||||
.ios-acc-body {
|
||||
border-top: 0;
|
||||
}
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// ACTIVITY BLOCK
|
||||
// =============================================================================
|
||||
|
||||
#activity {
|
||||
.p-4 {
|
||||
padding: 0 !important;
|
||||
}
|
||||
|
||||
.ios-section-title {
|
||||
padding: var(--space-2) var(--space-2) var(--space-1) !important;
|
||||
margin: 0 !important;
|
||||
}
|
||||
|
||||
.activity-feed {
|
||||
margin: 0 !important;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 1rem;
|
||||
|
||||
.card {
|
||||
width: 100% !important;
|
||||
max-width: none !important;
|
||||
border: 0 !important;
|
||||
box-shadow: none !important;
|
||||
border-radius: 0 !important;
|
||||
margin: 0 !important;
|
||||
}
|
||||
|
||||
.card-list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 1rem;
|
||||
}
|
||||
|
||||
.row > * {
|
||||
flex: 0 0 100% !important;
|
||||
max-width: 100% !important;
|
||||
}
|
||||
}
|
||||
|
||||
.card-actions {
|
||||
border-top: 0 !important;
|
||||
}
|
||||
|
||||
.collapse .bg-body-tertiary.border {
|
||||
border: 0 !important;
|
||||
}
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// SKILLS UI
|
||||
// =============================================================================
|
||||
|
||||
#skillsBlock[data-editable="1"] {
|
||||
.chip.chip-add {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
cursor: pointer;
|
||||
border-style: dashed;
|
||||
|
||||
&.pulse {
|
||||
transform: scale(0.97);
|
||||
transition: transform 0.15s ease;
|
||||
}
|
||||
|
||||
.bi {
|
||||
font-size: 0.95rem;
|
||||
}
|
||||
}
|
||||
|
||||
.chip.chip-skill:hover {
|
||||
.lbl {
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
.chip-del-btn,
|
||||
&:focus-within .chip-del-btn {
|
||||
visibility: visible;
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.chip.chip-skill {
|
||||
position: relative;
|
||||
padding: 0.35rem 0.65rem;
|
||||
|
||||
.lbl {
|
||||
transition: opacity 0.15s ease;
|
||||
}
|
||||
|
||||
.chip-del-btn {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background: transparent;
|
||||
border: 0;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
cursor: pointer;
|
||||
color: var(--bs-danger);
|
||||
font-size: 1rem;
|
||||
line-height: 1;
|
||||
visibility: hidden;
|
||||
opacity: 0;
|
||||
transition: opacity 0.15s ease;
|
||||
z-index: 1;
|
||||
|
||||
.bi {
|
||||
pointer-events: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.skills-empty {
|
||||
color: var(--bs-secondary-color);
|
||||
font-size: 0.875rem;
|
||||
margin-right: 0.25rem;
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// DEPT INLINE EDITOR
|
||||
// =============================================================================
|
||||
|
||||
.dept-edit-btn {
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
padding: 0;
|
||||
display: grid;
|
||||
place-items: center;
|
||||
border-radius: $badge-border-radius;
|
||||
border: 1px solid color-mix(in srgb, var(--bs-border-color), transparent 30%);
|
||||
background: color-mix(in srgb, var(--bs-body-bg) 94%, transparent);
|
||||
|
||||
&:hover {
|
||||
background: color-mix(in srgb, var(--bs-body-bg) 88%, transparent);
|
||||
}
|
||||
}
|
||||
|
||||
.dept-editor-wrap {
|
||||
flex: 0 0 100%;
|
||||
width: 100%;
|
||||
margin-top: 0.5rem;
|
||||
}
|
||||
|
||||
.dept-editor form {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
|
||||
.dept-role-select {
|
||||
min-width: 180px;
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// CONTAINER
|
||||
// =============================================================================
|
||||
|
||||
.container-xxl {
|
||||
max-width: $container-xxl-max-width;
|
||||
padding-inline: var(--bs-gutter-x, var(--space-2));
|
||||
}
|
||||
251
backend/static/scss_backup_20260115/components/_ios-search.scss
Normal file
251
backend/static/scss_backup_20260115/components/_ios-search.scss
Normal file
@ -0,0 +1,251 @@
|
||||
// =============================================================================
|
||||
// iOS-style Search Component (SCSS)
|
||||
// =============================================================================
|
||||
// Поле поиска в стиле iOS с иконкой лупы и кнопкой очистки
|
||||
// Переиспользуемый компонент для различных страниц списков
|
||||
//
|
||||
// HTML структура:
|
||||
// <div class="ios-search">
|
||||
// <i class="bi bi-search"></i>
|
||||
// <input type="text" class="ios-search-input" placeholder="Поиск...">
|
||||
// <button class="ios-search-clear" type="button">
|
||||
// <i class="bi bi-x-lg"></i>
|
||||
// </button>
|
||||
// </div>
|
||||
|
||||
// =============================================================================
|
||||
// ПЕРЕМЕННЫЕ
|
||||
// =============================================================================
|
||||
|
||||
// Размеры (default)
|
||||
$ios-search-height: 40px;
|
||||
$ios-search-max-width: 420px;
|
||||
$ios-search-border-radius: 14px;
|
||||
$ios-search-font-size: 15px;
|
||||
|
||||
// Размеры sm
|
||||
$ios-search-height-sm: 34px;
|
||||
$ios-search-border-radius-sm: 12px;
|
||||
$ios-search-font-size-sm: 14px;
|
||||
|
||||
// Размеры lg
|
||||
$ios-search-height-lg: 48px;
|
||||
$ios-search-border-radius-lg: 16px;
|
||||
$ios-search-font-size-lg: 16px;
|
||||
|
||||
// Размеры mobile
|
||||
$ios-search-height-mobile: 38px;
|
||||
$ios-search-font-size-mobile: 14px;
|
||||
|
||||
// Отступы
|
||||
$ios-search-padding-left: 36px;
|
||||
$ios-search-padding-right: 36px;
|
||||
$ios-search-icon-left: 10px;
|
||||
$ios-search-clear-right: 4px;
|
||||
|
||||
$ios-search-padding-left-sm: 32px;
|
||||
$ios-search-padding-right-sm: 32px;
|
||||
$ios-search-icon-left-sm: 8px;
|
||||
$ios-search-clear-right-sm: 3px;
|
||||
|
||||
$ios-search-padding-left-lg: 42px;
|
||||
$ios-search-padding-right-lg: 42px;
|
||||
$ios-search-icon-left-lg: 12px;
|
||||
$ios-search-clear-right-lg: 6px;
|
||||
|
||||
// Иконки
|
||||
$ios-search-icon-size: 0.95rem;
|
||||
$ios-search-icon-size-sm: 0.85rem;
|
||||
$ios-search-icon-size-lg: 1.1rem;
|
||||
$ios-search-clear-icon-size: 0.9rem;
|
||||
|
||||
// Переходы
|
||||
$ios-search-transition: box-shadow 0.2s ease, border-color 0.2s ease, background 0.2s ease;
|
||||
$ios-search-btn-transition: background 0.2s ease;
|
||||
|
||||
// =============================================================================
|
||||
// МИКСИНЫ
|
||||
// =============================================================================
|
||||
|
||||
// Миксин для скрытия нативного крестика браузера
|
||||
@mixin hide-search-controls {
|
||||
&::-webkit-search-cancel-button,
|
||||
&::-webkit-search-decoration {
|
||||
display: none;
|
||||
-webkit-appearance: none;
|
||||
}
|
||||
|
||||
&::-ms-clear {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// БАЗОВЫЕ СТИЛИ
|
||||
// =============================================================================
|
||||
|
||||
// Контейнер поиска
|
||||
.ios-search {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
max-width: $ios-search-max-width;
|
||||
}
|
||||
|
||||
// Поле ввода
|
||||
.ios-search-input {
|
||||
width: 100%;
|
||||
height: $ios-search-height;
|
||||
padding: var(--space-1) $ios-search-padding-right var(--space-1) $ios-search-padding-left;
|
||||
border-radius: $ios-search-border-radius;
|
||||
border: 1px solid var(--bs-border-color);
|
||||
background: color-mix(in srgb, var(--bs-body-bg), #000 4%);
|
||||
font-size: $ios-search-font-size;
|
||||
outline: none;
|
||||
transition: $ios-search-transition;
|
||||
|
||||
// Скрываем нативный крестик браузера
|
||||
@include hide-search-controls;
|
||||
|
||||
// Placeholder
|
||||
&::placeholder {
|
||||
color: var(--bs-secondary-color);
|
||||
opacity: 0.6;
|
||||
}
|
||||
|
||||
// Фокус
|
||||
&:focus {
|
||||
border-color: var(--bs-primary);
|
||||
box-shadow: 0 0 0 3px rgba(var(--bs-primary-rgb), 0.15);
|
||||
background: var(--bs-body-bg);
|
||||
}
|
||||
|
||||
// Темная тема
|
||||
[data-bs-theme="dark"] & {
|
||||
background: color-mix(in srgb, var(--bs-body-bg), #fff 6%);
|
||||
}
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// ИКОНКА ПОИСКА (ЛУПА)
|
||||
// =============================================================================
|
||||
|
||||
.ios-search > .bi-search {
|
||||
position: absolute;
|
||||
left: $ios-search-icon-left;
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
opacity: 0.55;
|
||||
pointer-events: none;
|
||||
font-size: $ios-search-icon-size;
|
||||
color: var(--bs-body-color);
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// КНОПКА ОЧИСТКИ
|
||||
// =============================================================================
|
||||
|
||||
.ios-search-clear {
|
||||
position: absolute;
|
||||
right: $ios-search-clear-right;
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
border: 0;
|
||||
background: transparent;
|
||||
padding: var(--space-1);
|
||||
display: none;
|
||||
cursor: pointer;
|
||||
border-radius: 50%;
|
||||
transition: $ios-search-btn-transition;
|
||||
|
||||
&:hover {
|
||||
background: color-mix(in srgb, var(--bs-body-color), transparent 90%);
|
||||
}
|
||||
|
||||
&:active {
|
||||
background: color-mix(in srgb, var(--bs-body-color), transparent 85%);
|
||||
}
|
||||
|
||||
.bi-x-lg {
|
||||
opacity: 0.6;
|
||||
font-size: $ios-search-clear-icon-size;
|
||||
color: var(--bs-body-color);
|
||||
}
|
||||
}
|
||||
|
||||
// Показываем кнопку очистки когда есть текст
|
||||
.ios-search-input:not(:placeholder-shown) ~ .ios-search-clear {
|
||||
display: block;
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// ВАРИАЦИИ РАЗМЕРОВ
|
||||
// =============================================================================
|
||||
|
||||
// Маленький размер
|
||||
.ios-search.sm {
|
||||
.ios-search-input {
|
||||
height: $ios-search-height-sm;
|
||||
font-size: $ios-search-font-size-sm;
|
||||
padding: var(--space-1) $ios-search-padding-right-sm var(--space-1) $ios-search-padding-left-sm;
|
||||
border-radius: $ios-search-border-radius-sm;
|
||||
}
|
||||
|
||||
> .bi-search {
|
||||
left: $ios-search-icon-left-sm;
|
||||
font-size: $ios-search-icon-size-sm;
|
||||
}
|
||||
|
||||
.ios-search-clear {
|
||||
right: $ios-search-clear-right-sm;
|
||||
padding: var(--space-1);
|
||||
}
|
||||
}
|
||||
|
||||
// Большой размер
|
||||
.ios-search.lg {
|
||||
.ios-search-input {
|
||||
height: $ios-search-height-lg;
|
||||
font-size: $ios-search-font-size-lg;
|
||||
padding: var(--space-2) $ios-search-padding-right-lg var(--space-2) $ios-search-padding-left-lg;
|
||||
border-radius: $ios-search-border-radius-lg;
|
||||
}
|
||||
|
||||
> .bi-search {
|
||||
left: $ios-search-icon-left-lg;
|
||||
font-size: $ios-search-icon-size-lg;
|
||||
}
|
||||
|
||||
.ios-search-clear {
|
||||
right: $ios-search-clear-right-lg;
|
||||
padding: var(--space-1);
|
||||
}
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// ВАРИАЦИЯ: ТОЛЬКО ИКОНКА БЕЗ КНОПКИ ОЧИСТКИ
|
||||
// =============================================================================
|
||||
|
||||
.ios-search.icon-only {
|
||||
.ios-search-input {
|
||||
padding-right: var(--space-3);
|
||||
}
|
||||
|
||||
.ios-search-clear {
|
||||
display: none !important;
|
||||
}
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// АДАПТИВНОСТЬ
|
||||
// =============================================================================
|
||||
|
||||
@media (max-width: 576px) {
|
||||
.ios-search {
|
||||
max-width: 100%;
|
||||
|
||||
.ios-search-input {
|
||||
height: $ios-search-height-mobile;
|
||||
font-size: $ios-search-font-size-mobile;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,219 @@
|
||||
// =============================================================================
|
||||
// Join Scroller Component (SCSS)
|
||||
// =============================================================================
|
||||
// Горизонтальная прокручиваемая лента новых сотрудников с hover-эффектами
|
||||
|
||||
// =============================================================================
|
||||
// ПЕРЕМЕННЫЕ
|
||||
// =============================================================================
|
||||
|
||||
$join-base-size: 56px; // базовый размер аватара
|
||||
$join-multiplier: 4; // множитель при раскрытии
|
||||
$join-multiplier-mobile: 3.2; // множитель на мобильных
|
||||
$join-gap: 0.9rem;
|
||||
$join-padding: 0.25rem;
|
||||
$join-transition-duration: 0.22s;
|
||||
|
||||
// =============================================================================
|
||||
// КОНТЕЙНЕР
|
||||
// =============================================================================
|
||||
|
||||
.join-wrap {
|
||||
padding: var(--space-2);
|
||||
border: 1px solid color-mix(in srgb, var(--bs-border-color), transparent 10%);
|
||||
border-radius: var(--feed-radius);
|
||||
background: var(--bs-body-bg);
|
||||
box-shadow: var(--feed-shadow);
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// ГОРИЗОНТАЛЬНАЯ ЛЕНТА
|
||||
// =============================================================================
|
||||
|
||||
.join-scroller {
|
||||
position: relative;
|
||||
overflow-x: auto;
|
||||
overflow-y: visible; // для инфо-блока
|
||||
scrollbar-width: none; // Firefox
|
||||
-ms-overflow-style: none; // IE/Edge
|
||||
|
||||
// Градиентная маска по краям
|
||||
mask-image: linear-gradient(
|
||||
to right,
|
||||
transparent 0%,
|
||||
black 40px,
|
||||
black calc(100% - 40px),
|
||||
transparent 100%
|
||||
);
|
||||
-webkit-mask-image: linear-gradient(
|
||||
to right,
|
||||
transparent 0%,
|
||||
black 40px,
|
||||
black calc(100% - 40px),
|
||||
transparent 100%
|
||||
);
|
||||
|
||||
&::-webkit-scrollbar {
|
||||
display: none; // Chrome/Safari
|
||||
}
|
||||
}
|
||||
|
||||
.join-rail {
|
||||
display: flex;
|
||||
flex-wrap: nowrap;
|
||||
align-items: flex-start;
|
||||
justify-content: center;
|
||||
gap: $join-gap;
|
||||
padding: $join-padding;
|
||||
min-width: 100%;
|
||||
}
|
||||
|
||||
@media (prefers-reduced-motion: reduce) {
|
||||
.join-scroller {
|
||||
scroll-behavior: auto !important;
|
||||
}
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// ЭЛЕМЕНТ ЛЕНТЫ
|
||||
// =============================================================================
|
||||
|
||||
.join-item {
|
||||
--base: #{$join-base-size};
|
||||
--mult: #{$join-multiplier};
|
||||
--size: var(--base);
|
||||
|
||||
position: relative;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
width: var(--size);
|
||||
flex: 0 0 var(--size);
|
||||
transition: width $join-transition-duration ease, flex-basis $join-transition-duration ease;
|
||||
|
||||
&:hover,
|
||||
&:focus-within {
|
||||
--size: calc(var(--base) * var(--mult));
|
||||
z-index: 2;
|
||||
}
|
||||
|
||||
// Отключение hover при перетаскивании
|
||||
&.no-hover {
|
||||
&:hover,
|
||||
&:focus-within {
|
||||
--size: var(--base);
|
||||
z-index: auto;
|
||||
}
|
||||
|
||||
.join-info {
|
||||
max-height: 0;
|
||||
opacity: 0;
|
||||
transform: translateY(-4px);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// АВАТАР
|
||||
// =============================================================================
|
||||
|
||||
.join-ava {
|
||||
width: var(--size);
|
||||
height: var(--size);
|
||||
border-radius: 999px;
|
||||
overflow: hidden;
|
||||
display: grid;
|
||||
place-items: center;
|
||||
border: 1px solid color-mix(in srgb, var(--bs-border-color), transparent 20%);
|
||||
background: var(--bs-tertiary-bg);
|
||||
transition: width $join-transition-duration ease,
|
||||
height $join-transition-duration ease,
|
||||
box-shadow $join-transition-duration ease,
|
||||
border-color $join-transition-duration ease;
|
||||
will-change: width, height;
|
||||
|
||||
img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: cover;
|
||||
display: block;
|
||||
background: var(--bs-tertiary-bg);
|
||||
}
|
||||
|
||||
.bi {
|
||||
font-size: 1.2rem;
|
||||
opacity: 0.65;
|
||||
}
|
||||
|
||||
.join-item:hover &,
|
||||
.join-item:focus-within & {
|
||||
box-shadow: var(--feed-shadow-hover);
|
||||
border-color: color-mix(in srgb, var(--bs-border-color), var(--bs-primary) 30%);
|
||||
}
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// ИНФО-БЛОК
|
||||
// =============================================================================
|
||||
|
||||
.join-info {
|
||||
width: 100%;
|
||||
overflow: hidden;
|
||||
max-height: 0;
|
||||
opacity: 0;
|
||||
transform: translateY(-4px);
|
||||
transition: max-height 0.24s ease, opacity 0.18s ease, transform 0.18s ease;
|
||||
|
||||
.join-item:hover &,
|
||||
.join-item:focus-within & {
|
||||
max-height: 320px; // с запасом для навыков
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
}
|
||||
|
||||
.name {
|
||||
font-weight: 800;
|
||||
line-height: 1.2;
|
||||
margin-top: 0.5rem;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.sub {
|
||||
font-size: 0.9rem;
|
||||
color: var(--bs-secondary-color);
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.skills-line {
|
||||
font-size: 0.9rem;
|
||||
text-align: center;
|
||||
margin-top: 0.35rem;
|
||||
word-break: break-word;
|
||||
white-space: normal;
|
||||
|
||||
b {
|
||||
font-weight: 800;
|
||||
}
|
||||
}
|
||||
|
||||
.link {
|
||||
display: inline-block;
|
||||
margin-top: 0.35rem;
|
||||
font-size: 0.9rem;
|
||||
text-decoration: none;
|
||||
|
||||
&:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// АДАПТИВНОСТЬ
|
||||
// =============================================================================
|
||||
|
||||
@media (max-width: 480px) {
|
||||
.join-item {
|
||||
--mult: #{$join-multiplier-mobile};
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,42 @@
|
||||
/**
|
||||
* layout-spacing.scss
|
||||
*
|
||||
* Централизованное управление отступами в app-layout
|
||||
* Все отступы контролируются через CSS переменные в variables.css
|
||||
*
|
||||
* ПРИНЦИП: Контейнеры занимают 100% пространства ячейки (без padding).
|
||||
* Все отступы между колонками управляются ТОЛЬКО через grid gap.
|
||||
*
|
||||
* Переменные для настройки (в variables.css):
|
||||
* --layout-gap: расстояние между sidebar, main, rightbar (по умолчанию 16px)
|
||||
* --layout-padding-y: вертикальные отступы layout (по умолчанию 24px)
|
||||
* --layout-padding-x: горизонтальные отступы layout (по умолчанию 0)
|
||||
*
|
||||
* Примеры изменения отступов:
|
||||
*
|
||||
* Увеличить расстояние между колонками до 24px:
|
||||
* :root { --layout-gap: var(--space-3); }
|
||||
*
|
||||
* Уменьшить расстояние между колонками до 8px:
|
||||
* :root { --layout-gap: var(--space-1); }
|
||||
*
|
||||
* Уменьшить вертикальные отступы до 16px:
|
||||
* :root { --layout-padding-y: var(--space-2); }
|
||||
*
|
||||
* Убрать вертикальные отступы:
|
||||
* :root { --layout-padding-y: 0; }
|
||||
*/
|
||||
|
||||
/* =====================================
|
||||
APP LAYOUT SPACING
|
||||
===================================== */
|
||||
|
||||
// Применяем вертикальные отступы к layout
|
||||
.app-layout {
|
||||
padding-top: var(--layout-padding-y);
|
||||
padding-bottom: var(--layout-padding-y);
|
||||
}
|
||||
|
||||
// Горизонтальные отступы управляются через container-xxl px-2 px-lg-3 в base.html
|
||||
// Grid gap между колонками управляется через --layout-gap в base-app.scss
|
||||
// Колонки (sidebar, main, rightbar) НЕ имеют собственных padding - занимают 100% ячейки
|
||||
148
backend/static/scss_backup_20260115/components/_list-view.scss
Normal file
148
backend/static/scss_backup_20260115/components/_list-view.scss
Normal file
@ -0,0 +1,148 @@
|
||||
// =============================================================================
|
||||
// List View Component (SCSS)
|
||||
// =============================================================================
|
||||
// Универсальные стили для списочных представлений (сотрудники, отделы)
|
||||
|
||||
// =============================================================================
|
||||
// ПЕРЕМЕННЫЕ
|
||||
// =============================================================================
|
||||
|
||||
$list-row-py: var(--space-1); // 8px вертикальные отступы
|
||||
$list-row-px: var(--space-2); // 16px горизонтальные отступы
|
||||
$list-gap: var(--space-2); // 16px расстояние внутри строки
|
||||
$list-avatar-size: 56px; // размер аватара
|
||||
|
||||
// =============================================================================
|
||||
// ТОКЕНЫ
|
||||
// =============================================================================
|
||||
|
||||
.list-view {
|
||||
--list-row-py: #{$list-row-py};
|
||||
--list-row-px: #{$list-row-px};
|
||||
--list-gap: #{$list-gap};
|
||||
--list-avatar: #{$list-avatar-size};
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// КОНТЕЙНЕР СПИСКОВ
|
||||
// =============================================================================
|
||||
|
||||
.list-view {
|
||||
background: transparent;
|
||||
padding: 0;
|
||||
|
||||
// Убираем gap от card-list
|
||||
&.card-list {
|
||||
display: block;
|
||||
gap: 0 !important;
|
||||
}
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// СТРОКИ СПИСКОВ
|
||||
// =============================================================================
|
||||
|
||||
.list-view .list-row {
|
||||
padding: var(--list-row-py) var(--list-row-px) !important;
|
||||
margin: 0 !important;
|
||||
border-bottom: 1px solid var(--bs-border-color);
|
||||
|
||||
&:last-of-type {
|
||||
border-bottom: 0;
|
||||
}
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// ШАПКА СТРОКИ
|
||||
// =============================================================================
|
||||
|
||||
.list-view .list-row .card-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: var(--list-gap);
|
||||
min-height: var(--list-avatar);
|
||||
margin: 0;
|
||||
padding: 0 !important;
|
||||
}
|
||||
|
||||
// Шапка с аватаром: grid layout
|
||||
.list-view .list-row:has(.card-icon) .card-header {
|
||||
display: grid;
|
||||
grid-template-columns: var(--list-avatar) 1fr auto;
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// АВАТАР
|
||||
// =============================================================================
|
||||
|
||||
.list-view .list-row .card-icon {
|
||||
width: var(--list-avatar);
|
||||
height: var(--list-avatar);
|
||||
border-radius: 50%;
|
||||
overflow: hidden;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background: color-mix(in srgb, var(--bs-secondary-bg), transparent 30%);
|
||||
flex: none;
|
||||
|
||||
img {
|
||||
display: block;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: cover;
|
||||
object-position: center;
|
||||
}
|
||||
|
||||
i {
|
||||
font-size: 1.5rem;
|
||||
line-height: 1;
|
||||
opacity: 0.6;
|
||||
}
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// КОНТЕНТ
|
||||
// =============================================================================
|
||||
|
||||
.list-view .list-row .card-meta {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.list-view .card-title {
|
||||
line-height: 1.25;
|
||||
}
|
||||
|
||||
.emp-pos {
|
||||
color: var(--bs-secondary-color);
|
||||
}
|
||||
|
||||
.list-view .card-subtitle {
|
||||
margin-top: var(--space-1);
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// ДЕЙСТВИЯ СПРАВА
|
||||
// =============================================================================
|
||||
|
||||
.emp-actions {
|
||||
justify-self: end;
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// УБИРАЕМ НЕНУЖНЫЕ ОТСТУПЫ
|
||||
// =============================================================================
|
||||
|
||||
.list-view .list-row .card-actions {
|
||||
padding: 0 !important;
|
||||
}
|
||||
|
||||
.list-view .list-row .card-body {
|
||||
padding: 0 !important;
|
||||
|
||||
&:empty {
|
||||
display: none !important;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,182 @@
|
||||
// =============================================================================
|
||||
// Logout Modal (iOS-style) (SCSS)
|
||||
// =============================================================================
|
||||
// iOS-style модальное окно выхода с анимациями
|
||||
|
||||
// =============================================================================
|
||||
// ПЕРЕМЕННЫЕ
|
||||
// =============================================================================
|
||||
|
||||
$ios-avatar-size: 64px;
|
||||
$ios-icon-size: 56px;
|
||||
$ios-btn-min-height: 48px;
|
||||
$ios-border-radius: 16px;
|
||||
$ios-border-radius-desktop: 18px;
|
||||
$ios-grip-width: 36px;
|
||||
$ios-grip-height: 4px;
|
||||
|
||||
// =============================================================================
|
||||
// OVERLAY
|
||||
// =============================================================================
|
||||
|
||||
.ios-overlay {
|
||||
position: fixed;
|
||||
inset: 0;
|
||||
z-index: 1080;
|
||||
background: color-mix(in srgb, var(--bs-body-bg), #000 35%);
|
||||
backdrop-filter: blur(10px) saturate(110%);
|
||||
display: grid;
|
||||
place-items: end center; // bottom sheet по умолчанию
|
||||
|
||||
@media (min-width: 992px) {
|
||||
place-items: center; // центрируем на десктопе
|
||||
}
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// SHEET / CARD
|
||||
// =============================================================================
|
||||
|
||||
.ios-sheet {
|
||||
width: 100%;
|
||||
max-width: 560px;
|
||||
background: color-mix(in srgb, var(--bs-body-bg), #fff 6%);
|
||||
border: 1px solid color-mix(in srgb, var(--bs-border-color), transparent 30%);
|
||||
border-radius: $ios-border-radius $ios-border-radius 0 0;
|
||||
box-shadow: 0 12px 48px rgba(0, 0, 0, 0.28);
|
||||
animation: slideUp 0.28s ease both;
|
||||
|
||||
@media (min-width: 992px) {
|
||||
border-radius: $ios-border-radius-desktop;
|
||||
animation: popIn 0.18s ease both;
|
||||
}
|
||||
}
|
||||
|
||||
// Грип-бар как в iOS
|
||||
.ios-grip {
|
||||
width: $ios-grip-width;
|
||||
height: $ios-grip-height;
|
||||
border-radius: 999px;
|
||||
background: color-mix(in srgb, var(--bs-body-color), transparent 70%);
|
||||
margin: var(--space-1) auto;
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// CONTENT
|
||||
// =============================================================================
|
||||
|
||||
.ios-body {
|
||||
padding: var(--space-2) var(--space-3) var(--space-3);
|
||||
|
||||
@media (min-width: 992px) {
|
||||
padding: var(--space-3);
|
||||
}
|
||||
}
|
||||
|
||||
.ios-title {
|
||||
margin: 0.25rem 0;
|
||||
text-align: center;
|
||||
font-weight: 800;
|
||||
letter-spacing: 0.2px;
|
||||
}
|
||||
|
||||
.ios-sub {
|
||||
text-align: center;
|
||||
color: var(--bs-secondary-color);
|
||||
margin: 0 0 1rem;
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// AVATAR & ICON
|
||||
// =============================================================================
|
||||
|
||||
.ios-ava {
|
||||
width: $ios-avatar-size;
|
||||
height: $ios-avatar-size;
|
||||
border-radius: 999px;
|
||||
overflow: hidden;
|
||||
border: 1px solid color-mix(in srgb, var(--bs-border-color), transparent 20%);
|
||||
background: var(--bs-tertiary-bg);
|
||||
display: grid;
|
||||
place-items: center;
|
||||
margin: 0.25rem auto 0.75rem;
|
||||
|
||||
img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: cover;
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
|
||||
.ios-ico {
|
||||
width: $ios-icon-size;
|
||||
height: $ios-icon-size;
|
||||
border-radius: 14px;
|
||||
background: color-mix(in srgb, var(--bs-danger), #000 12%);
|
||||
color: #fff;
|
||||
display: grid;
|
||||
place-items: center;
|
||||
margin: 0.25rem auto 0.75rem;
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// BUTTONS
|
||||
// =============================================================================
|
||||
|
||||
.ios-actions {
|
||||
display: grid;
|
||||
gap: 0.6rem;
|
||||
}
|
||||
|
||||
.ios-btn {
|
||||
min-height: $ios-btn-min-height;
|
||||
border-radius: 12px;
|
||||
font-weight: 700;
|
||||
|
||||
&-danger {
|
||||
background: var(--bs-danger);
|
||||
border-color: var(--bs-danger);
|
||||
color: #fff;
|
||||
|
||||
&:hover {
|
||||
filter: brightness(1.02);
|
||||
}
|
||||
}
|
||||
|
||||
&-ghost {
|
||||
background: color-mix(in srgb, var(--bs-body-bg), #fff 6%);
|
||||
border: 1px solid color-mix(in srgb, var(--bs-border-color), transparent 20%);
|
||||
color: var(--bs-body-color);
|
||||
|
||||
&:hover {
|
||||
background: color-mix(in srgb, var(--bs-body-bg), #fff 9%);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// ANIMATIONS
|
||||
// =============================================================================
|
||||
|
||||
@keyframes slideUp {
|
||||
from {
|
||||
transform: translateY(16px);
|
||||
opacity: 0;
|
||||
}
|
||||
to {
|
||||
transform: translateY(0);
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes popIn {
|
||||
from {
|
||||
transform: scale(0.98);
|
||||
opacity: 0;
|
||||
}
|
||||
to {
|
||||
transform: scale(1);
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,29 @@
|
||||
// =============================================================================
|
||||
// Modal Overrides (SCSS)
|
||||
// =============================================================================
|
||||
// Z-index corrections для модальных окон
|
||||
|
||||
// =============================================================================
|
||||
// MODAL Z-INDEX
|
||||
// =============================================================================
|
||||
|
||||
.modal {
|
||||
z-index: 1080 !important;
|
||||
|
||||
&.show {
|
||||
z-index: 1080 !important;
|
||||
}
|
||||
|
||||
// Не даём просвечивать через backdrop
|
||||
.modal-content {
|
||||
background-color: var(--bs-body-bg);
|
||||
}
|
||||
}
|
||||
|
||||
.modal-backdrop {
|
||||
z-index: 1075 !important;
|
||||
|
||||
&.show {
|
||||
z-index: 1075 !important;
|
||||
}
|
||||
}
|
||||
40
backend/static/scss_backup_20260115/components/_modals.scss
Normal file
40
backend/static/scss_backup_20260115/components/_modals.scss
Normal file
@ -0,0 +1,40 @@
|
||||
// =============================================================================
|
||||
// Стили для модальных окон
|
||||
// =============================================================================
|
||||
|
||||
// Скругление углов для всех модальных окон
|
||||
.modal-content {
|
||||
border-radius: 1rem !important; // Соответствует карточкам Bootstrap (16px)
|
||||
}
|
||||
|
||||
// Убираем скроллбар у modal-content
|
||||
.modal-dialog {
|
||||
.modal-content {
|
||||
overflow: hidden; // Скрываем скроллбар
|
||||
}
|
||||
}
|
||||
|
||||
// Специальные стили для модального окна поста
|
||||
#postDetailModal {
|
||||
.modal-dialog {
|
||||
max-width: 800px !important;
|
||||
width: 100%;
|
||||
margin: 1.75rem auto;
|
||||
}
|
||||
|
||||
.modal-body {
|
||||
max-height: 85vh;
|
||||
overflow-y: auto;
|
||||
padding: 0;
|
||||
background: var(--bs-body-bg) !important; // Непрозрачный фон
|
||||
|
||||
// Скрываем скроллбар
|
||||
scrollbar-width: none; // Firefox
|
||||
-ms-overflow-style: none; // IE и Edge
|
||||
|
||||
// Chrome, Safari, Edge
|
||||
&::-webkit-scrollbar {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
262
backend/static/scss_backup_20260115/components/_navbar.scss
Normal file
262
backend/static/scss_backup_20260115/components/_navbar.scss
Normal file
@ -0,0 +1,262 @@
|
||||
/**
|
||||
* navbar.scss
|
||||
* Стили навбара: меню-тогглер, поиск, уведомления
|
||||
*/
|
||||
|
||||
/* =====================================
|
||||
NAVBAR BASE
|
||||
===================================== */
|
||||
|
||||
// Navbar overflow visible для dropdown
|
||||
.app-navbar {
|
||||
overflow: visible !important;
|
||||
|
||||
.container-fluid {
|
||||
overflow: visible !important;
|
||||
}
|
||||
}
|
||||
|
||||
/* =====================================
|
||||
MOBILE NAVBAR HEIGHT
|
||||
===================================== */
|
||||
|
||||
// Увеличенная высота navbar для мобильных устройств
|
||||
@media (max-width: 767.98px) {
|
||||
.app-navbar {
|
||||
min-height: 90px;
|
||||
|
||||
.container-fluid {
|
||||
min-height: 64px;
|
||||
padding-top: 0.75rem;
|
||||
padding-bottom: 0.75rem;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* =====================================
|
||||
MENU TOGGLE (гамбургер)
|
||||
===================================== */
|
||||
|
||||
// Кнопка «гамбургера» — без круга/рамки
|
||||
.menu-toggle {
|
||||
padding: 0;
|
||||
border: 0;
|
||||
background: transparent;
|
||||
line-height: 1;
|
||||
color: #fff;
|
||||
|
||||
&:focus {
|
||||
outline: none;
|
||||
box-shadow: none;
|
||||
}
|
||||
}
|
||||
|
||||
/* =====================================
|
||||
DROPDOWN AVATAR
|
||||
===================================== */
|
||||
|
||||
// Спрятать стрелочку у dropdown-аватара
|
||||
.dropdown-toggle.no-caret::after {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
/* =====================================
|
||||
MOBILE SEARCH OVERLAY
|
||||
===================================== */
|
||||
|
||||
.mobile-search-overlay {
|
||||
position: fixed;
|
||||
top: var(--navbar-h);
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
background-color: rgba(0, 0, 0, 0.5);
|
||||
z-index: 1030;
|
||||
opacity: 0;
|
||||
pointer-events: none;
|
||||
transition: opacity 0.3s ease;
|
||||
|
||||
&.active {
|
||||
opacity: 1;
|
||||
pointer-events: auto;
|
||||
}
|
||||
}
|
||||
|
||||
// Светлая тема - забеливание
|
||||
[data-bs-theme="light"] .mobile-search-overlay {
|
||||
background-color: rgba(255, 255, 255, 0.85);
|
||||
}
|
||||
|
||||
// Темная тема - затемнение
|
||||
[data-bs-theme="dark"] .mobile-search-overlay {
|
||||
background-color: rgba(0, 0, 0, 0.7);
|
||||
}
|
||||
|
||||
/* =====================================
|
||||
BOTTOM NAVBAR (только для мобильных)
|
||||
===================================== */
|
||||
|
||||
.bottom-navbar {
|
||||
position: fixed;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
background-color: var(--bs-primary);
|
||||
border-top: 0.5px solid var(--bs-border-color);
|
||||
z-index: 1040;
|
||||
padding: 0;
|
||||
box-shadow: 0 -1px 10px rgba(0, 0, 0, 0.08);
|
||||
backdrop-filter: blur(10px);
|
||||
-webkit-backdrop-filter: blur(10px);
|
||||
}
|
||||
|
||||
// Темная тема
|
||||
[data-bs-theme="dark"] .bottom-navbar {
|
||||
background-color: var(--bs-tertiary-bg);
|
||||
border-top-color: var(--bs-border-color);
|
||||
box-shadow: 0 -1px 10px rgba(0, 0, 0, 0.3);
|
||||
}
|
||||
|
||||
.bottom-navbar-container {
|
||||
display: flex;
|
||||
justify-content: space-around;
|
||||
align-items: center;
|
||||
height: 60px;
|
||||
padding: 0 8px;
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
.bottom-nav-item {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
flex: 1;
|
||||
height: 100%;
|
||||
color: rgba(255, 255, 255, 0.7);
|
||||
text-decoration: none;
|
||||
border: none;
|
||||
background: transparent;
|
||||
padding: 4px 8px;
|
||||
transition: all 0.2s ease;
|
||||
position: relative;
|
||||
cursor: pointer;
|
||||
|
||||
&:hover,
|
||||
&:focus,
|
||||
&:active {
|
||||
color: #fff;
|
||||
background: rgba(255, 255, 255, 0.1);
|
||||
outline: none;
|
||||
}
|
||||
|
||||
i {
|
||||
font-size: 1.4rem;
|
||||
margin-bottom: 2px;
|
||||
line-height: 1;
|
||||
}
|
||||
}
|
||||
|
||||
// Темная тема - другой цвет кнопок
|
||||
[data-bs-theme="dark"] .bottom-nav-item {
|
||||
color: var(--bs-secondary-color);
|
||||
|
||||
&:hover,
|
||||
&:focus,
|
||||
&:active {
|
||||
color: var(--bs-emphasis-color);
|
||||
background: var(--bs-secondary-bg);
|
||||
}
|
||||
}
|
||||
|
||||
.bottom-nav-label {
|
||||
font-size: 0.65rem;
|
||||
font-weight: 500;
|
||||
text-align: center;
|
||||
line-height: 1;
|
||||
margin-top: 2px;
|
||||
}
|
||||
|
||||
/* =====================================
|
||||
BOTTOM NAV BADGE
|
||||
===================================== */
|
||||
|
||||
// Иконка с бейджем для уведомлений
|
||||
.bottom-nav-icon-wrapper {
|
||||
position: relative;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.bottom-nav-badge {
|
||||
position: absolute;
|
||||
top: -6px;
|
||||
right: -10px;
|
||||
background: var(--bs-danger);
|
||||
color: #fff;
|
||||
border-radius: 10px; // Telegram использует фиксированный radius для круглых бэйджей
|
||||
padding: 0 6px;
|
||||
font-size: 11px;
|
||||
font-weight: 600;
|
||||
min-width: 20px;
|
||||
height: 20px;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
text-align: center;
|
||||
line-height: 1;
|
||||
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2); // Более заметная тень
|
||||
transition: transform 0.2s cubic-bezier(0.175, 0.885, 0.32, 1.275),
|
||||
background-color 0.2s ease;
|
||||
z-index: 10;
|
||||
|
||||
// Мигание при новом сообщении
|
||||
&.pulse {
|
||||
animation: badge-pulse 0.6s ease-out;
|
||||
}
|
||||
|
||||
// Серый для замьюченных чатов
|
||||
&.badge-muted {
|
||||
background: var(--bs-secondary);
|
||||
}
|
||||
|
||||
&:empty {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes badge-pulse {
|
||||
0% {
|
||||
transform: scale(1);
|
||||
}
|
||||
50% {
|
||||
transform: scale(1.15);
|
||||
}
|
||||
100% {
|
||||
transform: scale(1);
|
||||
}
|
||||
}
|
||||
|
||||
/* =====================================
|
||||
MOBILE LAYOUT
|
||||
===================================== */
|
||||
|
||||
// Padding для body, чтобы контент не перекрывался нижней панелью
|
||||
@media (max-width: 767.98px) {
|
||||
body {
|
||||
padding-bottom: 60px;
|
||||
}
|
||||
|
||||
// Скрыть bottom navbar на странице чата - композер заменяет навигацию
|
||||
.chat-container ~ .bottom-navbar,
|
||||
.chat-page ~ .bottom-navbar,
|
||||
body:has(.chat-container) .bottom-navbar,
|
||||
body:has(.chat-page) .bottom-navbar {
|
||||
display: none;
|
||||
}
|
||||
|
||||
// Убрать padding, когда navbar скрыт
|
||||
body:has(.chat-container),
|
||||
body:has(.chat-page) {
|
||||
padding-bottom: 0;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,471 @@
|
||||
// =============================================================================
|
||||
// Notifications System Styles (SCSS)
|
||||
// =============================================================================
|
||||
// Стили для системы уведомлений: колокольчик, dropdown, toast, страница списка
|
||||
|
||||
// =============================================================================
|
||||
// ПЕРЕМЕННЫЕ
|
||||
// =============================================================================
|
||||
|
||||
// Колокольчик
|
||||
$bell-dot-size: 16px;
|
||||
$bell-dot-font-size: 9px;
|
||||
$bell-dot-offset-top: -4px;
|
||||
$bell-dot-offset-right: -6px;
|
||||
|
||||
// Dropdown
|
||||
$notification-dropdown-width: 360px;
|
||||
$notification-dropdown-max-height: 500px;
|
||||
$notification-dropdown-border-radius: 12px;
|
||||
$notification-dropdown-body-max-height: 300px;
|
||||
|
||||
// Элементы уведомлений
|
||||
$notification-item-padding: 12px 16px;
|
||||
$notification-icon-size: 36px;
|
||||
$notification-unread-badge-size: 8px;
|
||||
|
||||
// Цвета
|
||||
$notification-danger: #dc3545;
|
||||
$notification-primary: #0d6efd;
|
||||
$notification-unread-bg-light: #f0f7ff;
|
||||
$notification-unread-bg-dark: #1a3a52;
|
||||
|
||||
// Переходы
|
||||
$notification-transition: background-color 0.2s;
|
||||
|
||||
// =============================================================================
|
||||
// КОЛОКОЛЬЧИК В NAVBAR
|
||||
// =============================================================================
|
||||
|
||||
.bell-wrapper {
|
||||
position: relative;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.bell-dot {
|
||||
position: absolute;
|
||||
top: $bell-dot-offset-top;
|
||||
right: $bell-dot-offset-right;
|
||||
min-width: $bell-dot-size;
|
||||
height: $bell-dot-size;
|
||||
padding: 0 4px;
|
||||
background-color: $notification-danger;
|
||||
color: white;
|
||||
border-radius: calc($bell-dot-size / 2);
|
||||
font-size: $bell-dot-font-size;
|
||||
font-weight: 700;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
line-height: 1;
|
||||
box-shadow: 0 0 0 2px rgba(255, 255, 255, 0.3);
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// DROPDOWN МЕНЮ УВЕДОМЛЕНИЙ
|
||||
// =============================================================================
|
||||
|
||||
.notification-dropdown-menu {
|
||||
position: fixed;
|
||||
width: $notification-dropdown-width;
|
||||
max-height: $notification-dropdown-max-height;
|
||||
background: white;
|
||||
border-radius: $notification-dropdown-border-radius;
|
||||
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.15);
|
||||
z-index: 10000;
|
||||
overflow: hidden;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
[data-bs-theme="dark"] & {
|
||||
background: #2b3035;
|
||||
color: #dee2e6;
|
||||
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.4);
|
||||
}
|
||||
}
|
||||
|
||||
// Заголовок
|
||||
.notification-dropdown-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 12px 16px;
|
||||
border-bottom: 1px solid #e9ecef;
|
||||
background: #f8f9fa;
|
||||
|
||||
[data-bs-theme="dark"] & {
|
||||
background: #1f2327;
|
||||
border-bottom-color: #3d4349;
|
||||
}
|
||||
}
|
||||
|
||||
// Тело с прокруткой
|
||||
.notification-dropdown-body {
|
||||
flex: 1;
|
||||
overflow-y: auto;
|
||||
max-height: $notification-dropdown-body-max-height;
|
||||
scroll-behavior: smooth;
|
||||
-webkit-overflow-scrolling: touch;
|
||||
scrollbar-width: thin;
|
||||
scrollbar-gutter: stable both-edges;
|
||||
overscroll-behavior: contain;
|
||||
scrollbar-color: color-mix(in srgb, var(--bs-body-color), transparent 70%) transparent;
|
||||
|
||||
&:hover {
|
||||
scrollbar-color: color-mix(in srgb, var(--bs-body-color), transparent 55%) transparent;
|
||||
}
|
||||
|
||||
// Webkit scrollbar
|
||||
&::-webkit-scrollbar {
|
||||
width: 10px;
|
||||
height: 10px;
|
||||
}
|
||||
|
||||
&::-webkit-scrollbar-track {
|
||||
background: transparent;
|
||||
margin-block: 0;
|
||||
}
|
||||
|
||||
&::-webkit-scrollbar-thumb {
|
||||
background-color: color-mix(in srgb, var(--bs-body-color), transparent 75%);
|
||||
border-radius: 999px;
|
||||
border: 3px solid transparent;
|
||||
background-clip: padding-box;
|
||||
}
|
||||
|
||||
&:hover::-webkit-scrollbar-thumb {
|
||||
background-color: color-mix(in srgb, var(--bs-body-color), transparent 55%);
|
||||
}
|
||||
|
||||
// Темная тема scrollbar
|
||||
[data-bs-theme="dark"] & {
|
||||
scrollbar-color: color-mix(in srgb, var(--bs-body-color), transparent 78%) transparent;
|
||||
|
||||
&:hover {
|
||||
scrollbar-color: color-mix(in srgb, var(--bs-body-color), transparent 60%) transparent;
|
||||
}
|
||||
|
||||
&::-webkit-scrollbar-thumb {
|
||||
background-color: color-mix(in srgb, var(--bs-body-color), transparent 78%);
|
||||
}
|
||||
|
||||
&:hover::-webkit-scrollbar-thumb {
|
||||
background-color: color-mix(in srgb, var(--bs-body-color), transparent 60%);
|
||||
}
|
||||
}
|
||||
|
||||
// Скрытие scrollbar на тач-устройствах
|
||||
@media (hover: none), (pointer: coarse) {
|
||||
scrollbar-width: none;
|
||||
|
||||
&::-webkit-scrollbar {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Разделитель
|
||||
.notification-dropdown-divider {
|
||||
height: 1px;
|
||||
background: #e9ecef;
|
||||
margin: 0;
|
||||
|
||||
[data-bs-theme="dark"] & {
|
||||
background: #3d4349;
|
||||
}
|
||||
}
|
||||
|
||||
// Секция
|
||||
.notification-dropdown-section {
|
||||
padding: 8px 0;
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// ЭЛЕМЕНТЫ СПИСКА УВЕДОМЛЕНИЙ
|
||||
// =============================================================================
|
||||
|
||||
.notification-item {
|
||||
display: flex;
|
||||
gap: 12px;
|
||||
padding: $notification-item-padding;
|
||||
cursor: pointer;
|
||||
transition: $notification-transition;
|
||||
position: relative;
|
||||
border-bottom: 1px solid #e9ecef;
|
||||
|
||||
&:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
background-color: #f8f9fa;
|
||||
}
|
||||
|
||||
&.unread {
|
||||
background-color: $notification-unread-bg-light;
|
||||
|
||||
&:hover {
|
||||
background-color: #e6f2ff;
|
||||
}
|
||||
}
|
||||
|
||||
// Темная тема
|
||||
[data-bs-theme="dark"] & {
|
||||
border-bottom-color: #3d4349;
|
||||
|
||||
&:hover {
|
||||
background-color: #343a40;
|
||||
}
|
||||
|
||||
&.unread {
|
||||
background-color: $notification-unread-bg-dark;
|
||||
|
||||
&:hover {
|
||||
background-color: #1e4260;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Иконка уведомления
|
||||
.notification-icon {
|
||||
flex-shrink: 0;
|
||||
width: $notification-icon-size;
|
||||
height: $notification-icon-size;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: 20px;
|
||||
}
|
||||
|
||||
// Контент уведомления
|
||||
.notification-content {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.notification-title {
|
||||
font-weight: 600;
|
||||
font-size: 14px;
|
||||
margin-bottom: 4px;
|
||||
color: #212529;
|
||||
|
||||
[data-bs-theme="dark"] & {
|
||||
color: #dee2e6;
|
||||
}
|
||||
}
|
||||
|
||||
.notification-message {
|
||||
font-size: 13px;
|
||||
color: #6c757d;
|
||||
margin-bottom: 4px;
|
||||
|
||||
[data-bs-theme="dark"] & {
|
||||
color: #adb5bd;
|
||||
}
|
||||
}
|
||||
|
||||
.notification-time {
|
||||
font-size: 11px;
|
||||
color: #adb5bd;
|
||||
|
||||
[data-bs-theme="dark"] & {
|
||||
color: #6c757d;
|
||||
}
|
||||
}
|
||||
|
||||
// Значок непрочитанного
|
||||
.notification-unread-badge {
|
||||
flex-shrink: 0;
|
||||
width: $notification-unread-badge-size;
|
||||
height: $notification-unread-badge-size;
|
||||
background-color: $notification-primary;
|
||||
border-radius: 50%;
|
||||
margin-left: 8px;
|
||||
align-self: center;
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// ПУСТОЕ СОСТОЯНИЕ
|
||||
// =============================================================================
|
||||
|
||||
.notification-empty-state {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 48px 24px;
|
||||
color: #adb5bd;
|
||||
text-align: center;
|
||||
|
||||
i {
|
||||
font-size: 48px;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
div {
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
[data-bs-theme="dark"] & {
|
||||
color: #6c757d;
|
||||
}
|
||||
}
|
||||
|
||||
.extra-small {
|
||||
font-size: 0.7rem;
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// TOAST УВЕДОМЛЕНИЯ (FALLBACK)
|
||||
// =============================================================================
|
||||
|
||||
.toast-container {
|
||||
z-index: 9999;
|
||||
}
|
||||
|
||||
.toast {
|
||||
min-width: 300px;
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// СТРАНИЦА СПИСКА УВЕДОМЛЕНИЙ
|
||||
// =============================================================================
|
||||
|
||||
// Карточки уведомлений - прочитанные
|
||||
.notification-read {
|
||||
opacity: 0.7;
|
||||
|
||||
.card {
|
||||
background-color: #f8f9fa;
|
||||
}
|
||||
|
||||
[data-bs-theme="dark"] & {
|
||||
opacity: 0.65;
|
||||
|
||||
.card {
|
||||
background-color: #1f2327 !important;
|
||||
border-color: #3d4349 !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Карточки уведомлений - непрочитанные
|
||||
.notification-unread {
|
||||
border-left: 4px solid var(--bs-primary);
|
||||
|
||||
.card {
|
||||
background-color: #ffffff;
|
||||
}
|
||||
|
||||
[data-bs-theme="dark"] .card {
|
||||
background-color: #2b3035;
|
||||
border-color: #3d4349;
|
||||
}
|
||||
}
|
||||
|
||||
// Иконки категорий (для страницы списка)
|
||||
.notification-icon {
|
||||
width: 48px;
|
||||
height: 48px;
|
||||
border-radius: 50%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: 1.5rem;
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// ТЕМНАЯ ТЕМА ДЛЯ СТРАНИЦЫ СПИСКА
|
||||
// =============================================================================
|
||||
|
||||
[data-bs-theme="dark"] {
|
||||
.card {
|
||||
background-color: #2b3035;
|
||||
border-color: #3d4349;
|
||||
color: #dee2e6;
|
||||
}
|
||||
|
||||
.card-body {
|
||||
color: #dee2e6;
|
||||
}
|
||||
|
||||
.text-muted {
|
||||
color: #adb5bd !important;
|
||||
}
|
||||
|
||||
// Кнопки
|
||||
.btn-outline-primary {
|
||||
color: #6ea8fe;
|
||||
border-color: #6ea8fe;
|
||||
|
||||
&:hover {
|
||||
background-color: #0d6efd;
|
||||
border-color: #0d6efd;
|
||||
color: #fff;
|
||||
}
|
||||
}
|
||||
|
||||
.btn-outline-secondary {
|
||||
color: #adb5bd;
|
||||
border-color: #6c757d;
|
||||
|
||||
&:hover {
|
||||
background-color: #6c757d;
|
||||
border-color: #6c757d;
|
||||
color: #fff;
|
||||
}
|
||||
}
|
||||
|
||||
.btn-outline-danger {
|
||||
color: #ea868f;
|
||||
border-color: #ea868f;
|
||||
|
||||
&:hover {
|
||||
background-color: #dc3545;
|
||||
border-color: #dc3545;
|
||||
color: #fff;
|
||||
}
|
||||
}
|
||||
|
||||
// Формы
|
||||
.form-select,
|
||||
.form-control {
|
||||
background-color: #1f2327;
|
||||
border-color: #3d4349;
|
||||
color: #dee2e6;
|
||||
|
||||
&:focus {
|
||||
background-color: #2b3035;
|
||||
border-color: #6ea8fe;
|
||||
color: #dee2e6;
|
||||
}
|
||||
}
|
||||
|
||||
// Пагинация
|
||||
.pagination {
|
||||
.page-link {
|
||||
background-color: #2b3035;
|
||||
border-color: #3d4349;
|
||||
color: #6ea8fe;
|
||||
|
||||
&:hover {
|
||||
background-color: #343a40;
|
||||
border-color: #3d4349;
|
||||
color: #9ec5fe;
|
||||
}
|
||||
}
|
||||
|
||||
.page-item.active .page-link {
|
||||
background-color: #0d6efd;
|
||||
border-color: #0d6efd;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.page-item.disabled .page-link {
|
||||
background-color: #1f2327;
|
||||
border-color: #3d4349;
|
||||
color: #6c757d;
|
||||
}
|
||||
}
|
||||
}
|
||||
477
backend/static/scss_backup_20260115/components/_page-header.scss
Normal file
477
backend/static/scss_backup_20260115/components/_page-header.scss
Normal file
@ -0,0 +1,477 @@
|
||||
/**
|
||||
* _page-header.scss
|
||||
* Универсальная шапка для страниц списков с:
|
||||
* - Полупрозрачным фоном (sticky positioning)
|
||||
* - Иконкой и заголовком
|
||||
* - Счётчиком элементов
|
||||
* - Переключателем области (Мои/Все)
|
||||
* - Кнопкой фильтров
|
||||
* - Кнопкой создания (вытянутая на десктопе, иконка на мобильных)
|
||||
* - Поиском iOS-стиль
|
||||
*/
|
||||
|
||||
/* ─────────────────────────────────────────────────────────────
|
||||
ОСНОВНОЙ КОНТЕЙНЕР ШАПКИ
|
||||
────────────────────────────────────────────────────────────── */
|
||||
.page-header {
|
||||
position: sticky;
|
||||
top: var(--navbar-h, 60px);
|
||||
z-index: 10;
|
||||
padding: var(--space-2) 0;
|
||||
margin-bottom: var(--space-3);
|
||||
|
||||
/* Полупрозрачный фон с градиентом */
|
||||
background: linear-gradient(
|
||||
to bottom,
|
||||
color-mix(in srgb, var(--bs-body-bg) 90%, transparent) 0%,
|
||||
color-mix(in srgb, var(--bs-body-bg) 85%, transparent) 30%,
|
||||
color-mix(in srgb, var(--bs-body-bg) 70%, transparent) 50%,
|
||||
color-mix(in srgb, var(--bs-body-bg) 40%, transparent) 70%,
|
||||
transparent 100%
|
||||
);
|
||||
|
||||
@include transition((background, box-shadow), $transition-fast);
|
||||
|
||||
&::before {
|
||||
content: '';
|
||||
@include absolute-cover();
|
||||
backdrop-filter: saturate(180%) $blur-lg;
|
||||
-webkit-backdrop-filter: saturate(180%) $blur-lg;
|
||||
mask-image: linear-gradient(
|
||||
to bottom,
|
||||
black 0%,
|
||||
black 30%,
|
||||
rgba(0, 0, 0, 0.7) 50%,
|
||||
rgba(0, 0, 0, 0.4) 70%,
|
||||
transparent 100%
|
||||
);
|
||||
-webkit-mask-image: linear-gradient(
|
||||
to bottom,
|
||||
black 0%,
|
||||
black 30%,
|
||||
rgba(0, 0, 0, 0.7) 50%,
|
||||
rgba(0, 0, 0, 0.4) 70%,
|
||||
transparent 100%
|
||||
);
|
||||
pointer-events: none;
|
||||
z-index: -1;
|
||||
}
|
||||
|
||||
> * {
|
||||
position: relative;
|
||||
z-index: $z-base;
|
||||
}
|
||||
|
||||
/* При скролле - более выраженный фон */
|
||||
&.scrolled {
|
||||
background: linear-gradient(
|
||||
to bottom,
|
||||
color-mix(in srgb, var(--bs-body-bg) 95%, transparent) 0%,
|
||||
color-mix(in srgb, var(--bs-body-bg) 90%, transparent) 30%,
|
||||
color-mix(in srgb, var(--bs-body-bg) 75%, transparent) 50%,
|
||||
color-mix(in srgb, var(--bs-body-bg) 45%, transparent) 70%,
|
||||
transparent 100%
|
||||
);
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.04);
|
||||
}
|
||||
|
||||
/* Кнопка создания в виде карточки */
|
||||
.card {
|
||||
margin-top: var(--space-2);
|
||||
width: 100%;
|
||||
|
||||
button.dept-row,
|
||||
a.dept-row {
|
||||
cursor: pointer;
|
||||
transition: background-color 0.2s ease;
|
||||
width: 100%;
|
||||
padding: var(--space-1) var(--space-2);
|
||||
text-align: center;
|
||||
|
||||
.card-title {
|
||||
font-weight: 700;
|
||||
color: var(--bs-body-color);
|
||||
}
|
||||
|
||||
i {
|
||||
color: var(--bs-primary);
|
||||
}
|
||||
|
||||
&:hover {
|
||||
background-color: var(--bs-light);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Адаптивность */
|
||||
.d-flex.align-items-center {
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
}
|
||||
|
||||
/* ─────────────────────────────────────────────────────────────
|
||||
CHAT AVATAR WITH TYPE BADGE
|
||||
────────────────────────────────────────────────────────────── */
|
||||
.chat-avatar-wrapper {
|
||||
position: relative;
|
||||
width: 48px;
|
||||
height: 48px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.chat-avatar {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
border-radius: 12px;
|
||||
object-fit: cover;
|
||||
display: block;
|
||||
border: 1px solid var(--bs-border-color);
|
||||
}
|
||||
|
||||
.chat-type-badge {
|
||||
position: absolute;
|
||||
bottom: -2px;
|
||||
right: -2px;
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
border-radius: 50%;
|
||||
background: var(--bs-primary);
|
||||
color: white;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: 10px;
|
||||
border: 2px solid var(--bs-body-bg);
|
||||
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
|
||||
|
||||
i {
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
[data-bs-theme="dark"] & {
|
||||
border-color: var(--bs-body-bg);
|
||||
}
|
||||
}
|
||||
|
||||
/* ─────────────────────────────────────────────────────────────
|
||||
TOP BAR (заголовок + действия)
|
||||
────────────────────────────────────────────────────────────── */
|
||||
.page-header-top {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: var(--space-2);
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.page-header-title {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: var(--space-2);
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
|
||||
h1 {
|
||||
font-size: 1.5rem;
|
||||
font-weight: 700;
|
||||
margin: 0;
|
||||
color: var(--bs-body-color);
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
i {
|
||||
font-size: 1.75rem;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.page-header-actions {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: var(--space-1);
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
/* ─────────────────────────────────────────────────────────────
|
||||
ЗАГОЛОВОК И СЧЁТЧИК
|
||||
────────────────────────────────────────────────────────────── */
|
||||
.page-title {
|
||||
font-weight: 800;
|
||||
font-size: 1.75rem;
|
||||
letter-spacing: -0.01em;
|
||||
line-height: 1.2;
|
||||
color: var(--bs-body-color);
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.page-count {
|
||||
font-size: 0.875rem;
|
||||
color: var(--bs-secondary-color);
|
||||
white-space: nowrap;
|
||||
flex-shrink: 0;
|
||||
|
||||
.count-value {
|
||||
font-weight: 600;
|
||||
color: var(--bs-body-color);
|
||||
}
|
||||
}
|
||||
|
||||
/* ─────────────────────────────────────────────────────────────
|
||||
ПЕРЕКЛЮЧАТЕЛЬ ОБЛАСТИ (МОИ/ВСЕ)
|
||||
────────────────────────────────────────────────────────────── */
|
||||
.page-scope-switch {
|
||||
flex-shrink: 0;
|
||||
|
||||
.btn {
|
||||
min-width: 60px;
|
||||
font-size: 0.875rem;
|
||||
border-radius: 999px;
|
||||
padding: 0.375rem 0.75rem;
|
||||
transition: all 0.2s ease;
|
||||
|
||||
&:first-child {
|
||||
border-top-right-radius: 0;
|
||||
border-bottom-right-radius: 0;
|
||||
}
|
||||
|
||||
&:last-child {
|
||||
border-top-left-radius: 0;
|
||||
border-bottom-left-radius: 0;
|
||||
}
|
||||
|
||||
&.active {
|
||||
background-color: var(--bs-primary);
|
||||
border-color: var(--bs-primary);
|
||||
color: white;
|
||||
font-weight: 600;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* ─────────────────────────────────────────────────────────────
|
||||
КНОПКА ФИЛЬТРОВ
|
||||
────────────────────────────────────────────────────────────── */
|
||||
.page-filter-toggle {
|
||||
&:hover {
|
||||
background-color: color-mix(in srgb, var(--bs-primary), transparent 85%);
|
||||
border-color: var(--bs-primary);
|
||||
color: var(--bs-primary);
|
||||
}
|
||||
|
||||
&[aria-expanded="true"] {
|
||||
background-color: var(--bs-primary);
|
||||
border-color: var(--bs-primary);
|
||||
color: white;
|
||||
}
|
||||
}
|
||||
|
||||
/* ─────────────────────────────────────────────────────────────
|
||||
КНОПКА СОЗДАНИЯ
|
||||
────────────────────────────────────────────────────────────── */
|
||||
.page-create-btn {
|
||||
font-weight: 600;
|
||||
font-size: 0.875rem;
|
||||
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
|
||||
transition: all 0.2s ease;
|
||||
|
||||
&:hover {
|
||||
transform: translateY(-1px);
|
||||
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.15);
|
||||
}
|
||||
|
||||
&:active {
|
||||
transform: translateY(0);
|
||||
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
}
|
||||
|
||||
/* ─────────────────────────────────────────────────────────────
|
||||
ПОИСК
|
||||
────────────────────────────────────────────────────────────── */
|
||||
.page-search {
|
||||
flex-shrink: 1;
|
||||
min-width: 200px;
|
||||
max-width: 400px;
|
||||
flex-grow: 1;
|
||||
|
||||
.ios-search {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
/* ─────────────────────────────────────────────────────────────
|
||||
БЛОК ФИЛЬТРОВ
|
||||
────────────────────────────────────────────────────────────── */
|
||||
.page-filters-collapse {
|
||||
margin-top: var(--space-2);
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.page-filters {
|
||||
border-radius: var(--radius-lg, 14px);
|
||||
border: 1px solid color-mix(in srgb, var(--bs-border-color), transparent 10%);
|
||||
background: var(--bs-body-bg);
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);
|
||||
padding: var(--space-3);
|
||||
animation: slideDown 0.3s ease;
|
||||
|
||||
.btn {
|
||||
white-space: nowrap;
|
||||
min-width: fit-content;
|
||||
}
|
||||
|
||||
.d-flex.gap-2 {
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes slideDown {
|
||||
from {
|
||||
opacity: 0;
|
||||
transform: translateY(-10px);
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
}
|
||||
}
|
||||
|
||||
/* ─────────────────────────────────────────────────────────────
|
||||
АДАПТИВНОСТЬ
|
||||
────────────────────────────────────────────────────────────── */
|
||||
|
||||
/* Планшеты */
|
||||
@media (max-width: 991.98px) {
|
||||
.page-title {
|
||||
font-size: 1.5rem;
|
||||
}
|
||||
|
||||
.page-header {
|
||||
padding: var(--space-2) 0;
|
||||
}
|
||||
|
||||
.page-search {
|
||||
flex-basis: 100%;
|
||||
max-width: none;
|
||||
margin-top: var(--space-2);
|
||||
order: 10;
|
||||
}
|
||||
|
||||
.page-filter-toggle {
|
||||
order: 11;
|
||||
margin-top: var(--space-2);
|
||||
margin-left: auto;
|
||||
margin-right: 0 !important;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 767.98px) {
|
||||
.page-header-title {
|
||||
h1 {
|
||||
font-size: 1.25rem;
|
||||
}
|
||||
|
||||
i {
|
||||
font-size: 1.5rem;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Мобильные */
|
||||
@media (max-width: 575.98px) {
|
||||
.page-header {
|
||||
margin-bottom: var(--space-2);
|
||||
padding: var(--space-1) 0;
|
||||
top: var(--navbar-h, 60px);
|
||||
}
|
||||
|
||||
.page-title {
|
||||
font-size: 1.125rem;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.page-count {
|
||||
font-size: 0.7rem;
|
||||
}
|
||||
|
||||
.page-scope-switch {
|
||||
gap: 0.25rem;
|
||||
|
||||
.btn {
|
||||
min-width: auto;
|
||||
font-size: 0.7rem;
|
||||
padding: 0.2rem 0.4rem;
|
||||
}
|
||||
}
|
||||
|
||||
.page-filters {
|
||||
margin-top: 0.5rem;
|
||||
|
||||
.btn {
|
||||
padding: 0.4rem 0.75rem;
|
||||
font-size: 0.85rem;
|
||||
}
|
||||
}
|
||||
|
||||
.page-search {
|
||||
margin-top: 0.5rem;
|
||||
}
|
||||
|
||||
@media (max-width: 480px) {
|
||||
.page-count {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.page-header .card {
|
||||
margin-top: 0;
|
||||
width: 100%;
|
||||
|
||||
button.dept-row,
|
||||
a.dept-row {
|
||||
padding: var(--space-1) var(--space-1);
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* ─────────────────────────────────────────────────────────────
|
||||
ТЕМНАЯ ТЕМА
|
||||
────────────────────────────────────────────────────────────── */
|
||||
@media (prefers-color-scheme: dark) {
|
||||
.page-header {
|
||||
background: linear-gradient(
|
||||
to bottom,
|
||||
color-mix(in srgb, var(--bs-body-bg) 85%, transparent) 0%,
|
||||
color-mix(in srgb, var(--bs-body-bg) 80%, transparent) 45%,
|
||||
color-mix(in srgb, var(--bs-body-bg) 65%, transparent) 50%,
|
||||
color-mix(in srgb, var(--bs-body-bg) 35%, transparent) 70%,
|
||||
transparent 100%
|
||||
);
|
||||
border-bottom-color: color-mix(in srgb, var(--bs-border-color), transparent 60%);
|
||||
|
||||
&.scrolled {
|
||||
background: linear-gradient(
|
||||
to bottom,
|
||||
color-mix(in srgb, var(--bs-body-bg) 90%, transparent) 0%,
|
||||
color-mix(in srgb, var(--bs-body-bg) 80%, transparent) 30%,
|
||||
color-mix(in srgb, var(--bs-body-bg) 72%, transparent) 50%,
|
||||
color-mix(in srgb, var(--bs-body-bg) 40%, transparent) 70%,
|
||||
transparent 100%
|
||||
);
|
||||
}
|
||||
|
||||
.card {
|
||||
button.dept-row:hover,
|
||||
a.dept-row:hover {
|
||||
background-color: color-mix(in srgb, var(--bs-body-bg) 95%, white);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.page-filters {
|
||||
box-shadow: 0 2px 12px rgba(0, 0, 0, 0.3);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,20 @@
|
||||
// =============================================================================
|
||||
// Post Form Component (SCSS)
|
||||
// =============================================================================
|
||||
// Стили формы создания/редактирования поста
|
||||
|
||||
// =============================================================================
|
||||
// AUTOGROW TEXTAREA
|
||||
// =============================================================================
|
||||
|
||||
textarea.autogrow {
|
||||
overflow: hidden; // без скроллов до достижения max-height
|
||||
resize: none; // убираем ручной ресайз
|
||||
min-height: 120px;
|
||||
max-height: 70vh; // не занимаем весь экран
|
||||
|
||||
// Когда достигли max-height - включаем скролл
|
||||
&.is-max {
|
||||
overflow-y: auto;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,353 @@
|
||||
// =============================================================================
|
||||
// Recipient Picker (Email-style) Styles (SCSS)
|
||||
// =============================================================================
|
||||
// Компонент выбора получателей в стиле email-клиента
|
||||
|
||||
// =============================================================================
|
||||
// ПЕРЕМЕННЫЕ
|
||||
// =============================================================================
|
||||
|
||||
$recipient-avatar-size: 32px;
|
||||
$recipient-avatar-size-mobile: 28px;
|
||||
$recipient-label-width: 80px;
|
||||
$recipient-chip-padding: 0.25rem 0.5rem;
|
||||
$recipient-chip-padding-mobile: 0.2rem 0.4rem;
|
||||
|
||||
// Цвета
|
||||
$chip-primary-color: var(--bs-primary);
|
||||
$chip-primary-hover: var(--bs-primary-dark, #0056b3);
|
||||
$chip-secondary-color: var(--bs-secondary);
|
||||
$chip-secondary-hover: var(--bs-secondary-dark, #5a6268);
|
||||
|
||||
// =============================================================================
|
||||
// ОСНОВНОЙ КОНТЕЙНЕР
|
||||
// =============================================================================
|
||||
|
||||
.recipient-picker-email {
|
||||
font-size: 0.9375rem;
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// СТРОКИ ПОЛУЧАТЕЛЕЙ (To/CC/Отделы)
|
||||
// =============================================================================
|
||||
|
||||
.recipient-row {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
gap: 0.75rem;
|
||||
padding: 0.75rem 0;
|
||||
border-bottom: 1px solid var(--bs-border-color);
|
||||
|
||||
&:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// МЕТКА (To:/CC:/Отделы:)
|
||||
// =============================================================================
|
||||
|
||||
.recipient-label {
|
||||
min-width: $recipient-label-width;
|
||||
padding-top: 0.5rem;
|
||||
color: var(--bs-secondary);
|
||||
font-weight: 500;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
margin: 0;
|
||||
|
||||
i {
|
||||
font-size: 1.1em;
|
||||
}
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// ПОЛЕ ВВОДА
|
||||
// =============================================================================
|
||||
|
||||
.recipient-field {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
gap: 0.5rem;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// WRAPPER ДЛЯ CHIPS + INPUT
|
||||
// =============================================================================
|
||||
|
||||
.recipient-input-wrapper {
|
||||
flex: 1;
|
||||
min-width: 300px;
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
align-items: center;
|
||||
gap: 0.375rem;
|
||||
padding: 0.375rem 0.5rem;
|
||||
background: var(--bs-body-bg);
|
||||
border: 1px solid var(--bs-border-color);
|
||||
border-radius: var(--bs-border-radius);
|
||||
position: relative;
|
||||
transition: border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out;
|
||||
|
||||
&:focus-within {
|
||||
border-color: $chip-primary-color;
|
||||
box-shadow: 0 0 0 0.15rem rgba(var(--bs-primary-rgb), 0.15);
|
||||
}
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// INPUT ВНУТРИ WRAPPER
|
||||
// =============================================================================
|
||||
|
||||
.recipient-input {
|
||||
flex: 1;
|
||||
min-width: 200px;
|
||||
border: none;
|
||||
outline: none;
|
||||
padding: 0.25rem;
|
||||
font-size: inherit;
|
||||
background: transparent;
|
||||
|
||||
&::placeholder {
|
||||
color: var(--bs-secondary-color);
|
||||
opacity: 0.7;
|
||||
}
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// CHIPS (ВЫБРАННЫЕ ПОЛЬЗОВАТЕЛИ/ОТДЕЛЫ)
|
||||
// =============================================================================
|
||||
|
||||
.recipient-chip {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 0.375rem;
|
||||
padding: $recipient-chip-padding;
|
||||
background: $chip-primary-color;
|
||||
color: white;
|
||||
border-radius: 1rem;
|
||||
font-size: 0.875rem;
|
||||
white-space: nowrap;
|
||||
transition: background-color 0.15s;
|
||||
|
||||
&:hover {
|
||||
background: $chip-primary-hover;
|
||||
}
|
||||
|
||||
&.chip-cc {
|
||||
background: $chip-secondary-color;
|
||||
|
||||
&:hover {
|
||||
background: $chip-secondary-hover;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.chip-remove {
|
||||
background: none;
|
||||
border: none;
|
||||
padding: 0;
|
||||
color: white;
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
opacity: 0.8;
|
||||
transition: opacity 0.15s;
|
||||
|
||||
&:hover {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
i {
|
||||
font-size: 1.1em;
|
||||
line-height: 1;
|
||||
}
|
||||
}
|
||||
|
||||
// Chips для отделов
|
||||
.recipient-chips {
|
||||
display: inline-flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 0.375rem;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// DROPDOWN С AUTOCOMPLETE
|
||||
// =============================================================================
|
||||
|
||||
.recipient-dropdown {
|
||||
position: absolute;
|
||||
top: 100%;
|
||||
left: 0;
|
||||
right: 0;
|
||||
margin-top: 0.25rem;
|
||||
background: white;
|
||||
border: 1px solid var(--bs-border-color);
|
||||
border-radius: var(--bs-border-radius);
|
||||
box-shadow: 0 0.5rem 1rem rgba(0, 0, 0, 0.15);
|
||||
max-height: 300px;
|
||||
overflow-y: auto;
|
||||
z-index: 1050;
|
||||
}
|
||||
|
||||
.dropdown-item-user {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.75rem;
|
||||
padding: 0.625rem 1rem;
|
||||
cursor: pointer;
|
||||
transition: background-color 0.15s;
|
||||
|
||||
&:hover {
|
||||
background-color: var(--bs-light);
|
||||
}
|
||||
}
|
||||
|
||||
.dropdown-item-empty {
|
||||
padding: 0.75rem 1rem;
|
||||
color: var(--bs-secondary);
|
||||
text-align: center;
|
||||
font-size: 0.875rem;
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// АВАТАР ПОЛЬЗОВАТЕЛЯ
|
||||
// =============================================================================
|
||||
|
||||
.user-avatar-small {
|
||||
width: $recipient-avatar-size;
|
||||
height: $recipient-avatar-size;
|
||||
border-radius: 50%;
|
||||
background: $chip-primary-color;
|
||||
color: white;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-weight: 600;
|
||||
font-size: 0.75rem;
|
||||
flex-shrink: 0;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.user-avatar-img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: cover;
|
||||
border-radius: 50%;
|
||||
}
|
||||
|
||||
.user-avatar-placeholder {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background: $chip-primary-color;
|
||||
color: white;
|
||||
font-weight: 600;
|
||||
font-size: 0.75rem;
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// ИНФОРМАЦИЯ О ПОЛЬЗОВАТЕЛЕ
|
||||
// =============================================================================
|
||||
|
||||
.user-info {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.user-name {
|
||||
font-weight: 500;
|
||||
color: var(--bs-body-color);
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.user-subtitle {
|
||||
font-size: 0.8125rem;
|
||||
color: var(--bs-secondary);
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// ПОДСКАЗКИ
|
||||
// =============================================================================
|
||||
|
||||
.alert-sm {
|
||||
padding: 0.5rem 0.75rem;
|
||||
margin-top: 0.75rem;
|
||||
font-size: 0.875rem;
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// АДАПТИВНОСТЬ: ПЛАНШЕТЫ
|
||||
// =============================================================================
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.recipient-row {
|
||||
flex-direction: column;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
|
||||
.recipient-label {
|
||||
min-width: auto;
|
||||
padding-top: 0;
|
||||
}
|
||||
|
||||
.recipient-input-wrapper {
|
||||
min-width: 100%;
|
||||
}
|
||||
|
||||
.recipient-input {
|
||||
min-width: 150px;
|
||||
}
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// АДАПТИВНОСТЬ: МОБИЛЬНЫЕ
|
||||
// =============================================================================
|
||||
|
||||
@media (max-width: 576px) {
|
||||
.recipient-chip {
|
||||
font-size: 0.8125rem;
|
||||
padding: $recipient-chip-padding-mobile;
|
||||
}
|
||||
|
||||
.user-avatar-small {
|
||||
width: $recipient-avatar-size-mobile;
|
||||
height: $recipient-avatar-size-mobile;
|
||||
font-size: 0.7rem;
|
||||
}
|
||||
|
||||
.dropdown-item-user {
|
||||
padding: 0.5rem 0.75rem;
|
||||
}
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// ТЕМНАЯ ТЕМА
|
||||
// =============================================================================
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
.recipient-input-wrapper {
|
||||
background: var(--bs-dark);
|
||||
border-color: var(--bs-gray-700);
|
||||
}
|
||||
|
||||
.recipient-dropdown {
|
||||
background: var(--bs-dark);
|
||||
border-color: var(--bs-gray-700);
|
||||
}
|
||||
|
||||
.dropdown-item-user:hover {
|
||||
background-color: var(--bs-gray-800);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,225 @@
|
||||
// =============================================================================
|
||||
// Request Detail Styles (SCSS)
|
||||
// =============================================================================
|
||||
// Стили для страницы детального просмотра заявления
|
||||
|
||||
// =============================================================================
|
||||
// ПЕРЕМЕННЫЕ
|
||||
// =============================================================================
|
||||
|
||||
$req-detail-icon-size: 48px;
|
||||
$req-detail-icon-size-mobile: 40px;
|
||||
$req-detail-padding: 1rem 1.25rem;
|
||||
$req-detail-padding-mobile: 0.75rem 1rem;
|
||||
$req-info-label-width: 140px;
|
||||
|
||||
// =============================================================================
|
||||
// ЗАГОЛОВОК КАРТОЧКИ
|
||||
// =============================================================================
|
||||
|
||||
.request-detail-card .req-detail-header {
|
||||
padding: $req-detail-padding;
|
||||
border-bottom: 1px solid var(--bs-border-color);
|
||||
background: var(--bs-body-bg);
|
||||
}
|
||||
|
||||
.request-detail-card .req-detail-row {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
gap: 1rem;
|
||||
}
|
||||
|
||||
.request-detail-card .req-detail-icon {
|
||||
flex-shrink: 0;
|
||||
|
||||
.card-icon {
|
||||
width: $req-detail-icon-size;
|
||||
height: $req-detail-icon-size;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background: var(--bs-tertiary-bg);
|
||||
border-radius: 0.5rem;
|
||||
font-size: 1.5rem;
|
||||
}
|
||||
}
|
||||
|
||||
.request-detail-card .req-detail-content {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
|
||||
h1 {
|
||||
font-size: 1.25rem;
|
||||
font-weight: 600;
|
||||
margin: 0;
|
||||
line-height: 1.3;
|
||||
}
|
||||
}
|
||||
|
||||
.request-detail-card .req-detail-actions {
|
||||
display: flex;
|
||||
gap: 0.5rem;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// ИНФОРМАЦИОННЫЕ БЛОКИ
|
||||
// =============================================================================
|
||||
|
||||
.req-info-block {
|
||||
display: flex;
|
||||
padding: 0.75rem 0;
|
||||
border-bottom: 1px solid color-mix(in srgb, var(--bs-border-color), transparent 50%);
|
||||
|
||||
&:last-child {
|
||||
border-bottom: none;
|
||||
padding-bottom: 0;
|
||||
}
|
||||
|
||||
&:first-child {
|
||||
padding-top: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.req-info-label {
|
||||
flex: 0 0 $req-info-label-width;
|
||||
font-size: 0.875rem;
|
||||
color: var(--bs-secondary-color);
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.req-info-value {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
font-size: 0.9375rem;
|
||||
}
|
||||
|
||||
.req-comment-text {
|
||||
padding: 0.75rem 1rem;
|
||||
background: var(--bs-tertiary-bg);
|
||||
border-radius: 0.375rem;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// СЕКЦИЯ КОММЕНТАРИЕВ
|
||||
// =============================================================================
|
||||
|
||||
#comments-section {
|
||||
position: sticky;
|
||||
top: 1rem;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
max-height: 80vh;
|
||||
overflow: visible;
|
||||
|
||||
.card-header {
|
||||
background: var(--bs-body-bg);
|
||||
padding: 0.75rem 1rem;
|
||||
font-size: 0.9375rem;
|
||||
}
|
||||
|
||||
.card-body {
|
||||
flex: 1;
|
||||
min-height: 100px;
|
||||
overflow-y: auto;
|
||||
overflow-x: visible;
|
||||
}
|
||||
|
||||
.list-group-item {
|
||||
padding: 0.75rem 1rem;
|
||||
border-left: 0;
|
||||
border-right: 0;
|
||||
|
||||
&:first-child {
|
||||
border-top: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.card-footer {
|
||||
padding: 0.75rem 1rem;
|
||||
position: relative;
|
||||
z-index: 100;
|
||||
overflow: visible;
|
||||
flex-shrink: 0;
|
||||
border-top: 1px solid var(--bs-border-color);
|
||||
|
||||
.message-field {
|
||||
background: var(--bs-tertiary-bg);
|
||||
}
|
||||
}
|
||||
|
||||
.dropdown {
|
||||
position: static;
|
||||
}
|
||||
|
||||
.dropdown-menu {
|
||||
z-index: 2000 !important;
|
||||
position: fixed !important;
|
||||
max-height: 350px;
|
||||
overflow-y: auto;
|
||||
width: auto !important;
|
||||
min-width: 300px;
|
||||
}
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// АДАПТИВНОСТЬ: ПЛАНШЕТЫ
|
||||
// =============================================================================
|
||||
|
||||
@media (max-width: 992px) {
|
||||
.request-detail-card .req-detail-row {
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.request-detail-card .req-detail-content {
|
||||
flex: 1 1 calc(100% - 64px);
|
||||
}
|
||||
|
||||
.request-detail-card .req-detail-actions {
|
||||
width: 100%;
|
||||
margin-top: 0.75rem;
|
||||
}
|
||||
|
||||
#comments-section {
|
||||
position: static;
|
||||
}
|
||||
|
||||
.req-info-block {
|
||||
flex-direction: column;
|
||||
gap: 0.25rem;
|
||||
}
|
||||
|
||||
.req-info-label {
|
||||
flex: none;
|
||||
font-size: 0.75rem;
|
||||
}
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// АДАПТИВНОСТЬ: МОБИЛЬНЫЕ
|
||||
// =============================================================================
|
||||
|
||||
@media (max-width: 576px) {
|
||||
.request-detail-card .req-detail-header {
|
||||
padding: $req-detail-padding-mobile;
|
||||
}
|
||||
|
||||
.request-detail-card .req-detail-icon .card-icon {
|
||||
width: $req-detail-icon-size-mobile;
|
||||
height: $req-detail-icon-size-mobile;
|
||||
font-size: 1.25rem;
|
||||
}
|
||||
|
||||
.request-detail-card .req-detail-content h1 {
|
||||
font-size: 1.1rem;
|
||||
}
|
||||
|
||||
.request-detail-card .req-detail-actions {
|
||||
flex-direction: column;
|
||||
|
||||
.btn {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,545 @@
|
||||
// =============================================================================
|
||||
// Request List Styles (SCSS)
|
||||
// =============================================================================
|
||||
// Табличный вид списка заявлений с адаптивным дизайном
|
||||
|
||||
// =============================================================================
|
||||
// ПЕРЕМЕННЫЕ
|
||||
// =============================================================================
|
||||
|
||||
// Размеры колонок таблицы
|
||||
$req-icon-size: 48px;
|
||||
$req-icon-size-tablet: 42px;
|
||||
$req-icon-size-mobile: 36px;
|
||||
$req-icon-size-compact: 32px;
|
||||
|
||||
// Отступы
|
||||
$req-row-padding: 0.75rem 1rem;
|
||||
$req-row-padding-mobile: 1rem 0.75rem;
|
||||
$req-row-padding-compact: 0.625rem;
|
||||
$req-cell-gap: 0.75rem;
|
||||
$req-cell-gap-mobile: 0.75rem;
|
||||
|
||||
// Переходы
|
||||
$req-hover-transition: background-color 0.15s ease;
|
||||
|
||||
// =============================================================================
|
||||
// КОНТЕЙНЕР СПИСКА
|
||||
// =============================================================================
|
||||
|
||||
#reqList.card {
|
||||
display: block;
|
||||
gap: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// ЗАГОЛОВОК ТАБЛИЦЫ
|
||||
// =============================================================================
|
||||
|
||||
#reqList .req-table-header {
|
||||
position: sticky;
|
||||
top: 0;
|
||||
z-index: 10;
|
||||
background: var(--bs-body-bg);
|
||||
border-bottom: 2px solid var(--bs-border-color);
|
||||
|
||||
.req-table-row {
|
||||
padding: 0.625rem 1rem;
|
||||
}
|
||||
|
||||
h6 {
|
||||
font-size: 0.75rem;
|
||||
font-weight: 600;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.5px;
|
||||
color: var(--bs-secondary-color);
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.req-cell {
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
// Выравнивание заголовков
|
||||
.req-cell-title h6,
|
||||
.req-cell-author h6 {
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.req-cell-dates h6,
|
||||
.req-cell-status h6,
|
||||
.req-cell-created h6,
|
||||
.req-cell-actions h6 {
|
||||
text-align: center;
|
||||
}
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// СТРОКА ЗАЯВЛЕНИЯ
|
||||
// =============================================================================
|
||||
|
||||
#reqList .req-row {
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
border-bottom: 1px solid color-mix(in srgb, var(--bs-border-color), transparent 50%);
|
||||
transition: $req-hover-transition;
|
||||
|
||||
&:hover {
|
||||
background-color: color-mix(in srgb, var(--bs-primary) 3%, transparent);
|
||||
}
|
||||
|
||||
&:last-child {
|
||||
border-bottom: 0;
|
||||
}
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// ТАБЛИЧНАЯ СЕТКА
|
||||
// =============================================================================
|
||||
|
||||
#reqList .req-table-row {
|
||||
display: grid;
|
||||
grid-template-columns:
|
||||
$req-icon-size // Иконка
|
||||
minmax(200px, 2fr) // Заголовок (расширяемая)
|
||||
minmax(120px, 1fr) // Автор
|
||||
100px // Даты
|
||||
110px // Статус
|
||||
80px // Создано
|
||||
80px; // Действия
|
||||
gap: $req-cell-gap;
|
||||
align-items: start;
|
||||
padding: $req-row-padding;
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// ЯЧЕЙКИ ТАБЛИЦЫ
|
||||
// =============================================================================
|
||||
|
||||
#reqList .req-cell {
|
||||
min-width: 0; // Для корректного word-wrap
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
#reqList .req-cell-content {
|
||||
width: 100%;
|
||||
overflow-wrap: break-word;
|
||||
word-wrap: break-word;
|
||||
word-break: break-word;
|
||||
hyphens: auto;
|
||||
line-height: 1.4;
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// СПЕЦИФИЧНЫЕ СТИЛИ ЯЧЕЕК
|
||||
// =============================================================================
|
||||
|
||||
#reqList .req-cell-icon {
|
||||
justify-content: center;
|
||||
|
||||
.card-icon {
|
||||
width: 38px;
|
||||
height: 38px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
}
|
||||
|
||||
#reqList .req-cell-title {
|
||||
font-size: 0.9rem;
|
||||
|
||||
strong {
|
||||
display: block;
|
||||
margin-bottom: 0.125rem;
|
||||
}
|
||||
|
||||
.badge {
|
||||
font-size: 0.7rem;
|
||||
padding: 0.2rem 0.4rem;
|
||||
}
|
||||
}
|
||||
|
||||
#reqList .req-cell-author {
|
||||
font-size: 0.875rem;
|
||||
}
|
||||
|
||||
#reqList .req-cell-dates {
|
||||
font-size: 0.8rem;
|
||||
font-variant-numeric: tabular-nums;
|
||||
line-height: 1.3;
|
||||
}
|
||||
|
||||
#reqList .req-cell-status {
|
||||
justify-content: flex-start;
|
||||
|
||||
.badge {
|
||||
font-size: 0.7rem;
|
||||
padding: 0.25rem 0.5rem;
|
||||
white-space: nowrap;
|
||||
}
|
||||
}
|
||||
|
||||
#reqList .req-cell-created {
|
||||
font-size: 0.75rem;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
#reqList .req-cell-actions {
|
||||
justify-content: center;
|
||||
gap: 0.25rem;
|
||||
|
||||
.req-cell-content {
|
||||
display: flex;
|
||||
gap: 0.25rem;
|
||||
justify-content: center;
|
||||
}
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// ФУТЕР С КОММЕНТАРИЯМИ
|
||||
// =============================================================================
|
||||
|
||||
#reqList .req-footer {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
padding: 0.5rem 1rem;
|
||||
margin-left: calc($req-icon-size + $req-cell-gap);
|
||||
border-top: none;
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
#reqList .comments-block {
|
||||
margin: 0;
|
||||
padding: 1rem;
|
||||
margin-left: calc($req-icon-size + $req-cell-gap);
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// АДАПТИВНОСТЬ: ПЛАНШЕТЫ
|
||||
// =============================================================================
|
||||
|
||||
@media (max-width: 992px) {
|
||||
#reqList .req-table-header .req-table-row,
|
||||
#reqList .req-table-row {
|
||||
grid-template-columns:
|
||||
$req-icon-size-tablet // Иконка
|
||||
minmax(150px, 2fr) // Заголовок
|
||||
minmax(100px, 1fr) // Автор
|
||||
90px // Даты
|
||||
100px // Статус
|
||||
70px; // Создано
|
||||
}
|
||||
|
||||
// Скрываем колонку действий в заголовке
|
||||
#reqList .req-table-header .req-cell-actions {
|
||||
display: none;
|
||||
}
|
||||
|
||||
// Но кнопки остаются видимыми
|
||||
#reqList .req-cell-actions {
|
||||
grid-column: 6;
|
||||
display: flex !important;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// АДАПТИВНОСТЬ: МОБИЛЬНЫЕ
|
||||
// =============================================================================
|
||||
|
||||
@media (max-width: 768px) {
|
||||
// Скрываем заголовок таблицы
|
||||
#reqList .req-table-header {
|
||||
display: none;
|
||||
}
|
||||
|
||||
// Переходим на flexbox
|
||||
#reqList .req-table-row {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
flex-wrap: wrap;
|
||||
padding: $req-row-padding-mobile;
|
||||
gap: $req-cell-gap-mobile;
|
||||
align-items: flex-start;
|
||||
}
|
||||
|
||||
// Сброс для ячеек
|
||||
#reqList .req-cell {
|
||||
display: block;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
#reqList .req-cell-content {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
// Иконка слева сверху
|
||||
#reqList .req-cell-icon {
|
||||
flex: 0 0 $req-icon-size-mobile;
|
||||
padding-top: 0.25rem;
|
||||
|
||||
.card-icon {
|
||||
width: $req-icon-size-compact;
|
||||
height: $req-icon-size-compact;
|
||||
}
|
||||
}
|
||||
|
||||
// Заголовок занимает оставшуюся ширину
|
||||
#reqList .req-cell-title {
|
||||
flex: 1 1 calc(100% - 48px);
|
||||
font-size: 0.9rem;
|
||||
|
||||
strong {
|
||||
display: block;
|
||||
margin-bottom: 0.25rem;
|
||||
font-size: 0.95rem;
|
||||
}
|
||||
|
||||
.badge {
|
||||
display: inline-block;
|
||||
font-size: 0.7rem;
|
||||
padding: 0.2rem 0.4rem;
|
||||
}
|
||||
|
||||
.small {
|
||||
display: block;
|
||||
font-size: 0.8rem;
|
||||
margin-top: 0.25rem;
|
||||
}
|
||||
}
|
||||
|
||||
// Остальная информация
|
||||
#reqList .req-cell-author,
|
||||
#reqList .req-cell-dates,
|
||||
#reqList .req-cell-status {
|
||||
flex: 0 1 auto;
|
||||
font-size: 0.8rem;
|
||||
}
|
||||
|
||||
#reqList .req-cell-dates {
|
||||
white-space: nowrap;
|
||||
|
||||
br {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
#reqList .req-cell-status .badge {
|
||||
font-size: 0.7rem;
|
||||
padding: 0.25rem 0.5rem;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
// Скрываем дату создания
|
||||
#reqList .req-cell-created {
|
||||
display: none;
|
||||
}
|
||||
|
||||
// Кнопки действий на отдельной строке
|
||||
#reqList .req-cell-actions {
|
||||
display: flex !important;
|
||||
flex: 1 1 100%;
|
||||
gap: 0.5rem;
|
||||
padding-top: 0.75rem;
|
||||
border-top: 1px solid var(--bs-border-color-translucent);
|
||||
margin: 0;
|
||||
|
||||
.req-cell-content {
|
||||
display: flex;
|
||||
gap: 0.5rem;
|
||||
flex-wrap: wrap;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.btn {
|
||||
padding: 0.5rem 0.75rem;
|
||||
font-size: 0.85rem;
|
||||
}
|
||||
}
|
||||
|
||||
// Футер на полную ширину
|
||||
#reqList .req-footer {
|
||||
flex: 1 1 100%;
|
||||
margin-left: 0;
|
||||
padding: 0.75rem 0;
|
||||
border-top: 1px solid var(--bs-border-color-translucent);
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 0.5rem;
|
||||
|
||||
.btn {
|
||||
padding: 0.4rem 0.6rem;
|
||||
font-size: 0.8rem;
|
||||
}
|
||||
}
|
||||
|
||||
// Блок комментариев
|
||||
#reqList .comments-block {
|
||||
margin-left: 0;
|
||||
padding: 0.75rem;
|
||||
}
|
||||
|
||||
// Строка без hover
|
||||
#reqList .req-row {
|
||||
padding: 0;
|
||||
border-bottom: 1px solid color-mix(in srgb, var(--bs-border-color), transparent 50%);
|
||||
|
||||
&:hover {
|
||||
background-color: transparent;
|
||||
}
|
||||
|
||||
&:last-child {
|
||||
border-bottom: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// КОМПАКТНЫЙ РЕЖИМ
|
||||
// =============================================================================
|
||||
|
||||
@media (max-width: 576px) {
|
||||
#reqList .req-table-row {
|
||||
padding: $req-row-padding-compact;
|
||||
}
|
||||
|
||||
#reqList .req-cell-title .small {
|
||||
display: none; // Скрываем превью
|
||||
}
|
||||
|
||||
#reqList .req-cell-dates {
|
||||
br {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.req-cell-content::after {
|
||||
content: ' — ';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// AJAX-РЕЖИМ: REQ-ITEM
|
||||
// =============================================================================
|
||||
|
||||
.req-item {
|
||||
border-bottom: 1px solid var(--bs-border-color);
|
||||
padding: 1rem;
|
||||
transition: background-color 0.2s;
|
||||
|
||||
&:hover {
|
||||
background-color: var(--bs-light);
|
||||
}
|
||||
|
||||
&:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
}
|
||||
|
||||
.req-header {
|
||||
margin-bottom: 0.75rem;
|
||||
}
|
||||
|
||||
.req-title-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 0.75rem;
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
.req-link {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
text-decoration: none;
|
||||
color: var(--bs-body-color);
|
||||
font-weight: 500;
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
|
||||
&:hover {
|
||||
color: var(--bs-primary);
|
||||
}
|
||||
|
||||
strong {
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
}
|
||||
|
||||
.req-meta {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 1rem;
|
||||
font-size: 0.875rem;
|
||||
color: var(--bs-secondary-color);
|
||||
|
||||
> span {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.25rem;
|
||||
}
|
||||
}
|
||||
|
||||
.req-comment {
|
||||
margin: 0.5rem 0;
|
||||
padding: 0.5rem;
|
||||
background-color: var(--bs-light);
|
||||
border-radius: 0.375rem;
|
||||
font-size: 0.875rem;
|
||||
color: var(--bs-secondary-color);
|
||||
}
|
||||
|
||||
.req-footer {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 0.75rem;
|
||||
align-items: center;
|
||||
padding-top: 0.75rem;
|
||||
border-top: 1px solid var(--bs-border-color-translucent);
|
||||
font-size: 0.875rem;
|
||||
}
|
||||
|
||||
.req-approver,
|
||||
.req-recipients {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.25rem;
|
||||
color: var(--bs-secondary-color);
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// BADGE СТАТУСЫ
|
||||
// =============================================================================
|
||||
|
||||
.badge-warning {
|
||||
background-color: #ffc107;
|
||||
color: #000;
|
||||
}
|
||||
|
||||
.badge-success {
|
||||
background-color: #198754;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.badge-danger {
|
||||
background-color: #dc3545;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.badge-secondary {
|
||||
background-color: #6c757d;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// ВСПОМОГАТЕЛЬНЫЕ
|
||||
// =============================================================================
|
||||
|
||||
.load-more-trigger {
|
||||
visibility: hidden;
|
||||
}
|
||||
@ -0,0 +1,331 @@
|
||||
/**
|
||||
* rightbar-calendar.scss
|
||||
* Стили для компонента правой панели с календарем
|
||||
* Включает стили для FullCalendar, вертикальной ленты недели и модалов событий
|
||||
*/
|
||||
|
||||
/* =====================================
|
||||
БАЗА КАЛЕНДАРЯ
|
||||
===================================== */
|
||||
|
||||
.calendar-wrap {
|
||||
min-height: 360px;
|
||||
border-radius: 10px;
|
||||
overflow: hidden;
|
||||
background: var(--bs-body-bg);
|
||||
box-shadow: inset 0 1px 3px color-mix(in srgb, var(--bs-body-color), transparent 92%);
|
||||
border: 0;
|
||||
}
|
||||
|
||||
.rightbar-card {
|
||||
height: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
border-radius: 16px;
|
||||
overflow: hidden;
|
||||
border: 1px solid color-mix(in srgb, var(--bs-border-color), transparent 10%);
|
||||
background: var(--bs-body-bg);
|
||||
box-shadow: 0 1px 2px rgba(0,0,0,.04), 0 8px 24px rgba(0,0,0,.08);
|
||||
min-height: 0;
|
||||
|
||||
.card-header {
|
||||
flex: 0 0 auto;
|
||||
background: var(--bs-tertiary-bg);
|
||||
border-bottom: 1px solid var(--bs-border-color);
|
||||
padding: var(--space-1) var(--space-2);
|
||||
}
|
||||
|
||||
.card-body {
|
||||
flex: 1 1 auto;
|
||||
min-height: 0;
|
||||
overflow-y: auto;
|
||||
-webkit-overflow-scrolling: touch;
|
||||
padding: var(--space-2);
|
||||
}
|
||||
}
|
||||
|
||||
// Поддержка многострочного header при длинных названиях отделов
|
||||
.min-w-0 {
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
/* =====================================
|
||||
DESKTOP STICKY LAYOUT
|
||||
===================================== */
|
||||
|
||||
// На десктопе rightbar становится sticky с фиксированной высотой
|
||||
// Высота = 100vh - navbar - 2*отступ (сверху и снизу)
|
||||
// top = navbar + отступ сверху
|
||||
// Не перемещается с основным скролом страницы, скролл только внутри .card-body
|
||||
@media (min-width: 992px) {
|
||||
.rightbar {
|
||||
position: sticky;
|
||||
top: calc(var(--navbar-h) + var(--layout-padding-y, 12px));
|
||||
height: calc(100dvh - var(--navbar-h) - 2 * var(--layout-padding-y, 12px));
|
||||
overflow: hidden;
|
||||
// padding управляется через Bootstrap классы в шаблоне (px-0, py-3 и т.д.)
|
||||
}
|
||||
}
|
||||
|
||||
/* =====================================
|
||||
FULLCALENDAR — КОМПАКТНО
|
||||
===================================== */
|
||||
|
||||
.fc {
|
||||
font-size: 0.7rem;
|
||||
--fc-border-color: var(--bs-border-color);
|
||||
--fc-page-bg-color: transparent;
|
||||
--fc-neutral-bg-color: transparent;
|
||||
--fc-today-bg-color: color-mix(in srgb, var(--bs-primary), transparent 85%);
|
||||
--fc-event-bg-color: var(--bs-primary);
|
||||
--fc-event-border-color: var(--bs-primary);
|
||||
--fc-event-text-color: var(--bs-body-bg);
|
||||
|
||||
// Убираем границы
|
||||
.fc-theme-standard .fc-scrollgrid {
|
||||
border: 0 !important;
|
||||
}
|
||||
|
||||
.fc-theme-standard td,
|
||||
.fc-theme-standard th {
|
||||
border: 0 !important;
|
||||
}
|
||||
|
||||
// Toolbar
|
||||
.fc-toolbar {
|
||||
padding: var(--space-2);
|
||||
background: var(--bs-tertiary-bg);
|
||||
border-bottom: 1px solid var(--bs-border-color);
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.fc-toolbar-title {
|
||||
font-size: 1rem;
|
||||
font-weight: 600;
|
||||
color: var(--bs-body-color);
|
||||
}
|
||||
|
||||
.fc-button {
|
||||
border-radius: 8px;
|
||||
font-size: .82rem;
|
||||
padding: var(--space-1);
|
||||
background: transparent;
|
||||
border: none;
|
||||
color: var(--bs-primary);
|
||||
|
||||
&:hover {
|
||||
background: rgba(var(--bs-primary-rgb), .1);
|
||||
}
|
||||
}
|
||||
|
||||
// Day cells
|
||||
.fc-daygrid-day-frame {
|
||||
padding: 2px 3px !important;
|
||||
}
|
||||
|
||||
.fc-daygrid-day-number {
|
||||
font-size: .82rem;
|
||||
width: 100%;
|
||||
height: 1.6rem;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-weight: 600;
|
||||
color: var(--bs-body-color);
|
||||
text-decoration: none !important;
|
||||
}
|
||||
|
||||
.fc-day-today {
|
||||
background: none !important;
|
||||
|
||||
.fc-daygrid-day-number {
|
||||
color: var(--bs-primary);
|
||||
font-weight: 700;
|
||||
border: 1px solid var(--bs-primary);
|
||||
border-radius: 50%;
|
||||
}
|
||||
}
|
||||
|
||||
.fc-day-other .fc-daygrid-day-number {
|
||||
opacity: .4;
|
||||
}
|
||||
|
||||
// Events
|
||||
.fc-daygrid-day-events {
|
||||
padding: 0 2px 2px !important;
|
||||
row-gap: 2px !important;
|
||||
}
|
||||
|
||||
.fc-daygrid-event-harness {
|
||||
margin: 0 0 2px 0 !important;
|
||||
}
|
||||
|
||||
.fc-daygrid-event {
|
||||
border-radius: 6px;
|
||||
padding: var(--space-1) !important;
|
||||
line-height: 1.02 !important;
|
||||
background: color-mix(in srgb, var(--bs-primary), var(--bs-body-bg) 75%);
|
||||
border: 1px solid color-mix(in srgb, var(--bs-primary), transparent 60%);
|
||||
color: var(--bs-body-color);
|
||||
|
||||
.fc-event-time {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
.fc-event-title {
|
||||
font-size: .62rem !important;
|
||||
line-height: 1.02 !important;
|
||||
letter-spacing: -0.2px;
|
||||
font-weight: 600;
|
||||
font-family: "Arial Narrow","Roboto Condensed",system-ui,-apple-system,"Segoe UI",Roboto,Helvetica,Arial,sans-serif;
|
||||
display: -webkit-box !important;
|
||||
-webkit-line-clamp: 3;
|
||||
-webkit-box-orient: vertical;
|
||||
overflow: hidden !important;
|
||||
white-space: normal !important;
|
||||
text-overflow: ellipsis;
|
||||
max-height: calc(1.02em * 3);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* =====================================
|
||||
ВЕРТИКАЛЬНАЯ ЛЕНТА НЕДЕЛИ
|
||||
===================================== */
|
||||
|
||||
.week-strip-header {
|
||||
margin-top: var(--space-2);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
color: var(--bs-secondary-color);
|
||||
font-size: .8rem;
|
||||
}
|
||||
|
||||
.week-vertical {
|
||||
margin-top: var(--space-1);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--space-1);
|
||||
|
||||
.week-row {
|
||||
display: grid;
|
||||
grid-template-columns: 56px 1fr;
|
||||
align-items: start;
|
||||
gap: var(--space-1);
|
||||
padding: var(--space-1);
|
||||
border-radius: 12px;
|
||||
background: color-mix(in srgb, var(--bs-primary), var(--bs-body-bg) 86%);
|
||||
border: 1px solid color-mix(in srgb, var(--bs-primary), transparent 70%);
|
||||
|
||||
&:hover {
|
||||
background: color-mix(in srgb, var(--bs-primary), var(--bs-body-bg) 82%);
|
||||
}
|
||||
}
|
||||
|
||||
.content {
|
||||
.title {
|
||||
font-size: .8rem;
|
||||
font-weight: 600;
|
||||
color: var(--bs-body-color);
|
||||
display: -webkit-box;
|
||||
-webkit-line-clamp: 2;
|
||||
-webkit-box-orient: vertical;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: normal;
|
||||
}
|
||||
|
||||
.meta {
|
||||
margin-top: .15rem;
|
||||
font-size: .72rem;
|
||||
color: var(--bs-secondary-color);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.date-badge {
|
||||
display: grid;
|
||||
place-items: center;
|
||||
border-radius: 10px;
|
||||
padding: .35rem .25rem;
|
||||
background: color-mix(in srgb, var(--bs-primary), var(--bs-body-bg) 92%);
|
||||
border: 1px solid color-mix(in srgb, var(--bs-primary), transparent 75%);
|
||||
text-align: center;
|
||||
line-height: 1.05;
|
||||
|
||||
.dow {
|
||||
font-size: .7rem;
|
||||
font-weight: 700;
|
||||
color: var(--bs-primary);
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
.md {
|
||||
font-size: .74rem;
|
||||
font-weight: 700;
|
||||
color: var(--bs-body-color);
|
||||
}
|
||||
}
|
||||
|
||||
/* =====================================
|
||||
OFFCANVAS (МОБАЙЛ)
|
||||
===================================== */
|
||||
|
||||
#rightbarOffcanvas {
|
||||
height: 100dvh;
|
||||
height: 100svh;
|
||||
|
||||
.offcanvas-body {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--space-1);
|
||||
height: calc(100dvh - var(--rb-offcanvas-head, 56px));
|
||||
height: calc(100svh - var(--rb-offcanvas-head, 56px));
|
||||
min-height: 0;
|
||||
overflow-y: auto;
|
||||
-webkit-overflow-scrolling: touch;
|
||||
overscroll-behavior: contain;
|
||||
}
|
||||
|
||||
.calendar-wrap {
|
||||
flex: 0 0 auto;
|
||||
}
|
||||
|
||||
.week-vertical {
|
||||
flex: 0 0 auto;
|
||||
}
|
||||
}
|
||||
|
||||
/* =====================================
|
||||
COLOR PICKER (PALETTE)
|
||||
===================================== */
|
||||
|
||||
.color-swatch {
|
||||
width: 26px;
|
||||
height: 26px;
|
||||
border: 1px solid var(--bs-border-color);
|
||||
border-radius: 9999px;
|
||||
padding: 0;
|
||||
cursor: pointer;
|
||||
background: transparent;
|
||||
|
||||
&.active {
|
||||
outline: 2px solid var(--bs-primary);
|
||||
outline-offset: 2px;
|
||||
}
|
||||
}
|
||||
|
||||
/* =====================================
|
||||
EVENT DETAILS MODAL
|
||||
===================================== */
|
||||
|
||||
.color-dot {
|
||||
width: 12px;
|
||||
height: 12px;
|
||||
border-radius: 9999px;
|
||||
display: inline-block;
|
||||
vertical-align: middle;
|
||||
margin-right: 6px;
|
||||
border: 1px solid #00000022;
|
||||
}
|
||||
198
backend/static/scss_backup_20260115/components/_sidebar.scss
Normal file
198
backend/static/scss_backup_20260115/components/_sidebar.scss
Normal file
@ -0,0 +1,198 @@
|
||||
/**
|
||||
* _sidebar.scss
|
||||
* Стили левого сайдбара: навигация, активные элементы, переключатель темы
|
||||
*/
|
||||
|
||||
/* ─────────────────────────────────────────────────────────────
|
||||
SIDEBAR LAYOUT
|
||||
────────────────────────────────────────────────────────────── */
|
||||
/* Offcanvas остаётся как есть (липкий ≥ lg) */
|
||||
#sidebar {
|
||||
--bs-offcanvas-border-color: transparent;
|
||||
--bs-offcanvas-border-width: 0;
|
||||
--bs-offcanvas-border-radius: 0;
|
||||
border: 0 !important;
|
||||
border-radius: 0 !important;
|
||||
box-shadow: none !important;
|
||||
background: var(--bs-body-bg);
|
||||
}
|
||||
|
||||
/* На десктопе sidebar становится sticky с фиксированной высотой
|
||||
Высота = 100vh - navbar - 2*отступ (сверху и снизу)
|
||||
top = navbar + отступ сверху
|
||||
Не перемещается с основным скролом страницы, имеет свой внутренний скролл */
|
||||
@media (min-width: 992px) {
|
||||
#sidebar.offcanvas-lg {
|
||||
position: sticky;
|
||||
top: calc(var(--navbar-h, 60px) + var(--layout-padding-y, 12px));
|
||||
height: calc(100dvh - var(--navbar-h, 60px) - 2 * var(--layout-padding-y, 12px));
|
||||
align-self: start;
|
||||
overflow: auto;
|
||||
border-right: .5px solid var(--bs-border-color);
|
||||
transform: none !important;
|
||||
visibility: visible !important;
|
||||
}
|
||||
}
|
||||
|
||||
/* ─────────────────────────────────────────────────────────────
|
||||
NAVIGATION SECTIONS
|
||||
────────────────────────────────────────────────────────────── */
|
||||
.side-section {
|
||||
margin: var(--space-1) 0 var(--space-2);
|
||||
}
|
||||
|
||||
.side-hd {
|
||||
font-size: .78rem;
|
||||
font-weight: 800;
|
||||
letter-spacing: .03em;
|
||||
text-transform: uppercase;
|
||||
color: var(--bs-secondary-color);
|
||||
padding: var(--space-1);
|
||||
margin: 0 0 var(--space-1) var(--space-1);
|
||||
}
|
||||
|
||||
.side-nav {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: .15rem;
|
||||
}
|
||||
|
||||
/* ─────────────────────────────────────────────────────────────
|
||||
NAV LINKS
|
||||
────────────────────────────────────────────────────────────── */
|
||||
.side-nav {
|
||||
.nav-link {
|
||||
position: relative;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: .6rem;
|
||||
padding: .48rem .55rem;
|
||||
border-radius: $radius-md;
|
||||
color: var(--bs-body-color);
|
||||
text-decoration: none;
|
||||
border: 0;
|
||||
background: transparent !important;
|
||||
line-height: 1.15;
|
||||
@include transition((background, color, transform, box-shadow), $transition-fast);
|
||||
outline: none;
|
||||
|
||||
/* мягкий ховер */
|
||||
&:hover {
|
||||
color: var(--bs-primary);
|
||||
background: color-mix(in srgb, var(--bs-primary) 10%, transparent);
|
||||
}
|
||||
|
||||
/* видимая клавиатурная фокусировка */
|
||||
&:focus-visible {
|
||||
@include focus-ring();
|
||||
}
|
||||
|
||||
/* активная «пилюля» + тонкий индикатор слева */
|
||||
&.active {
|
||||
color: var(--bs-primary);
|
||||
font-weight: 800;
|
||||
background: color-mix(in srgb, var(--bs-primary) 16%, transparent);
|
||||
|
||||
&::before {
|
||||
content: "";
|
||||
position: absolute;
|
||||
left: -6px;
|
||||
top: 8px;
|
||||
bottom: 8px;
|
||||
width: 3px;
|
||||
border-radius: 2px;
|
||||
background: var(--bs-primary);
|
||||
}
|
||||
}
|
||||
|
||||
/* «отключённый» пункт выглядит реально disabled */
|
||||
&.disabled {
|
||||
opacity: .5;
|
||||
pointer-events: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* ─────────────────────────────────────────────────────────────
|
||||
ICONS & BADGES
|
||||
────────────────────────────────────────────────────────────── */
|
||||
.side-icon {
|
||||
width: 1.35rem;
|
||||
height: 1.35rem;
|
||||
display: inline-grid;
|
||||
place-items: center;
|
||||
flex: 0 0 auto;
|
||||
|
||||
> i {
|
||||
font-size: 1.1rem;
|
||||
line-height: 1;
|
||||
}
|
||||
}
|
||||
|
||||
.icon-wrap {
|
||||
position: relative;
|
||||
display: inline-grid;
|
||||
place-items: center;
|
||||
}
|
||||
|
||||
.nav-unread {
|
||||
position: absolute;
|
||||
top: -4px;
|
||||
right: -8px;
|
||||
min-width: 16px;
|
||||
height: 16px;
|
||||
padding: 0 var(--space-1);
|
||||
border-radius: 9999px;
|
||||
font-size: 10px;
|
||||
line-height: 16px;
|
||||
font-weight: 700;
|
||||
background: var(--bs-danger);
|
||||
color: #fff;
|
||||
border: 1px solid rgba(255, 255, 255, .6);
|
||||
|
||||
[data-bs-theme="dark"] & {
|
||||
border-color: rgba(0, 0, 0, .25);
|
||||
}
|
||||
}
|
||||
|
||||
/* ─────────────────────────────────────────────────────────────
|
||||
THEME SWITCHER BUTTON
|
||||
────────────────────────────────────────────────────────────── */
|
||||
.btn-theme {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: .5rem;
|
||||
min-height: 36px;
|
||||
padding: .35rem .75rem;
|
||||
border-radius: 999px;
|
||||
background: var(--bs-tertiary-bg);
|
||||
color: var(--bs-body-color);
|
||||
border: 1px solid var(--bs-border-color);
|
||||
transition: transform .08s ease, background-color .25s ease, border-color .25s ease;
|
||||
|
||||
&:hover {
|
||||
transform: translateY(-1px);
|
||||
background: color-mix(in srgb, var(--bs-tertiary-bg) 90%, var(--bs-primary) 10%);
|
||||
border-color: color-mix(in srgb, var(--bs-border-color), var(--bs-primary) 30%);
|
||||
}
|
||||
|
||||
&:focus-visible {
|
||||
outline: 0;
|
||||
box-shadow: 0 0 0 2px rgba(var(--bs-primary-rgb), .25);
|
||||
}
|
||||
|
||||
.bi {
|
||||
font-size: 1rem;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
.mode-label {
|
||||
font-weight: 700;
|
||||
font-size: .85rem;
|
||||
opacity: .9;
|
||||
|
||||
@media (max-width: 576px) {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,136 @@
|
||||
/**
|
||||
* spacing-utils.scss
|
||||
* Утилиты для точного контроля отступов
|
||||
*
|
||||
* Использование CSS переменных из variables.css для унифицированных отступов
|
||||
*/
|
||||
|
||||
/* =====================================
|
||||
GAP UTILITIES
|
||||
===================================== */
|
||||
|
||||
// Gap для flex/grid контейнеров
|
||||
.gap-component {
|
||||
gap: var(--gap-component); // 16px - стандартный gap между элементами
|
||||
}
|
||||
|
||||
.gap-section {
|
||||
gap: var(--gap-section); // 24px - gap между секциями
|
||||
}
|
||||
|
||||
.gap-1 {
|
||||
gap: var(--space-1); // 8px
|
||||
}
|
||||
|
||||
.gap-2 {
|
||||
gap: var(--space-2); // 16px
|
||||
}
|
||||
|
||||
.gap-3 {
|
||||
gap: var(--space-3); // 24px
|
||||
}
|
||||
|
||||
.gap-4 {
|
||||
gap: var(--space-4); // 32px
|
||||
}
|
||||
|
||||
/* =====================================
|
||||
MARGIN UTILITIES
|
||||
===================================== */
|
||||
|
||||
// Margin bottom для секций и блоков
|
||||
.mb-section {
|
||||
margin-bottom: var(--margin-section); // 24px - между секциями
|
||||
}
|
||||
|
||||
.mb-block {
|
||||
margin-bottom: var(--margin-block); // 32px - между блоками
|
||||
}
|
||||
|
||||
// Margin на всех сторонах
|
||||
.my-section {
|
||||
margin-block: var(--margin-section); // 24px сверху/снизу
|
||||
}
|
||||
|
||||
.my-block {
|
||||
margin-block: var(--margin-block); // 32px сверху/снизу
|
||||
}
|
||||
|
||||
/* =====================================
|
||||
PADDING UTILITIES
|
||||
===================================== */
|
||||
|
||||
// Padding для карточек
|
||||
.p-card {
|
||||
padding: var(--card-padding); // 16px со всех сторон
|
||||
}
|
||||
|
||||
.p-card-sm {
|
||||
padding: var(--card-padding-sm); // 8px со всех сторон
|
||||
}
|
||||
|
||||
.p-card-lg {
|
||||
padding: var(--card-padding-lg); // 24px со всех сторон
|
||||
}
|
||||
|
||||
// Padding по осям
|
||||
.px-card {
|
||||
padding-inline: var(--card-padding); // 16px лево/право
|
||||
}
|
||||
|
||||
.py-card {
|
||||
padding-block: var(--card-padding); // 16px верх/низ
|
||||
}
|
||||
|
||||
.px-card-sm {
|
||||
padding-inline: var(--card-padding-sm); // 8px лево/право
|
||||
}
|
||||
|
||||
.py-card-sm {
|
||||
padding-block: var(--card-padding-sm); // 8px верх/низ
|
||||
}
|
||||
|
||||
.px-card-lg {
|
||||
padding-inline: var(--card-padding-lg); // 24px лево/право
|
||||
}
|
||||
|
||||
.py-card-lg {
|
||||
padding-block: var(--card-padding-lg); // 24px верх/низ
|
||||
}
|
||||
|
||||
/* =====================================
|
||||
RESPONSIVE UTILITIES
|
||||
===================================== */
|
||||
|
||||
// Адаптивный gap для мобильных
|
||||
@media (max-width: 576px) {
|
||||
.gap-component-mobile {
|
||||
gap: var(--space-1); // 8px на мобильных
|
||||
}
|
||||
|
||||
.p-card-mobile {
|
||||
padding: var(--card-padding-sm); // 8px на мобильных
|
||||
}
|
||||
}
|
||||
|
||||
/* =====================================
|
||||
КОМБИНИРОВАННЫЕ КЛАССЫ
|
||||
===================================== */
|
||||
|
||||
// Стандартная секция с отступами
|
||||
.section-spacing {
|
||||
margin-bottom: var(--margin-section); // 24px снизу
|
||||
padding: var(--card-padding); // 16px внутри
|
||||
}
|
||||
|
||||
// Компактная секция
|
||||
.section-spacing-sm {
|
||||
margin-bottom: var(--space-2); // 16px снизу
|
||||
padding: var(--card-padding-sm); // 8px внутри
|
||||
}
|
||||
|
||||
// Просторная секция
|
||||
.section-spacing-lg {
|
||||
margin-bottom: var(--margin-block); // 32px снизу
|
||||
padding: var(--card-padding-lg); // 24px внутри
|
||||
}
|
||||
210
backend/static/scss_backup_20260115/components/_team-wheel.scss
Normal file
210
backend/static/scss_backup_20260115/components/_team-wheel.scss
Normal file
@ -0,0 +1,210 @@
|
||||
// =============================================================================
|
||||
// Team Wheel Component (SCSS)
|
||||
// =============================================================================
|
||||
// Круговой прокручиваемый виджет сотрудников
|
||||
|
||||
// =============================================================================
|
||||
// ПЕРЕМЕННЫЕ
|
||||
// =============================================================================
|
||||
|
||||
$wheel-size: 200px;
|
||||
$ava-size: 52px;
|
||||
$arc-width: 4px;
|
||||
|
||||
// =============================================================================
|
||||
// CSS ПЕРЕМЕННЫЕ
|
||||
// =============================================================================
|
||||
|
||||
:root {
|
||||
--wheel-size: #{$wheel-size};
|
||||
--ava-size: #{$ava-size};
|
||||
--ring-bg: radial-gradient(
|
||||
ellipse at center,
|
||||
var(--bs-body-bg),
|
||||
color-mix(in srgb, var(--bs-body-bg) 85%, var(--bs-secondary-bg, #f7f7f9) 15%)
|
||||
);
|
||||
--ring-border: var(--bs-border-color, rgba(0, 0, 0, 0.125));
|
||||
--shadow-inset: 0 8px 24px rgba(0, 0, 0, 0.06) inset;
|
||||
--shadow-out: 0 4px 12px rgba(0, 0, 0, 0.05);
|
||||
--arc-width: #{$arc-width};
|
||||
--arc-track: color-mix(in srgb, var(--bs-secondary-color) 30%, transparent);
|
||||
--arc-prog: var(--bs-primary);
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// TEAM WRAP & WHEEL
|
||||
// =============================================================================
|
||||
|
||||
.team-wrap {
|
||||
width: var(--wheel-size);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
margin-inline: auto;
|
||||
}
|
||||
|
||||
.team-wheel {
|
||||
position: relative;
|
||||
width: var(--wheel-size);
|
||||
height: var(--wheel-size);
|
||||
border-radius: 50%;
|
||||
overflow: hidden;
|
||||
background: var(--ring-bg);
|
||||
border: 1px solid var(--ring-border);
|
||||
box-shadow: var(--shadow-inset);
|
||||
user-select: none;
|
||||
touch-action: none;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// WHEEL LAYOUT
|
||||
// =============================================================================
|
||||
|
||||
.wheel-plane {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
}
|
||||
|
||||
.wheel-columns {
|
||||
position: absolute;
|
||||
inset: 10px var(--space-1);
|
||||
display: flex;
|
||||
gap: var(--space-1);
|
||||
justify-content: space-between;
|
||||
align-items: flex-start;
|
||||
will-change: transform;
|
||||
}
|
||||
|
||||
.wheel-col {
|
||||
width: calc(25% - 6px);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--space-1);
|
||||
align-items: center;
|
||||
|
||||
&.col-2 {
|
||||
margin-top: calc(var(--ava-size) * 0.5);
|
||||
}
|
||||
|
||||
&.col-3 {
|
||||
margin-top: calc(var(--ava-size) * 1);
|
||||
}
|
||||
|
||||
&.col-4 {
|
||||
margin-top: calc(var(--ava-size) * 1.5);
|
||||
}
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// WHEEL ITEMS
|
||||
// =============================================================================
|
||||
|
||||
.wheel-item {
|
||||
background: transparent;
|
||||
border: 0;
|
||||
padding: 0;
|
||||
width: var(--ava-size);
|
||||
height: var(--ava-size);
|
||||
border-radius: 50%;
|
||||
cursor: pointer;
|
||||
box-shadow: var(--shadow-out);
|
||||
|
||||
.ava {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
border-radius: 50%;
|
||||
overflow: hidden;
|
||||
display: grid;
|
||||
place-items: center;
|
||||
background: var(--bs-tertiary-bg);
|
||||
border: 1px solid var(--bs-border-color);
|
||||
|
||||
img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: cover;
|
||||
}
|
||||
|
||||
.bi {
|
||||
font-size: 1.2rem;
|
||||
opacity: 0.5;
|
||||
}
|
||||
}
|
||||
|
||||
&:active {
|
||||
transform: scale(0.96);
|
||||
}
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// PROGRESS RING
|
||||
// =============================================================================
|
||||
|
||||
.progress-ring {
|
||||
position: absolute;
|
||||
inset: -1px;
|
||||
transform: rotate(-90deg);
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.progress-ring__circle {
|
||||
stroke: var(--arc-track);
|
||||
fill: none;
|
||||
stroke-width: var(--arc-width);
|
||||
stroke-linecap: round;
|
||||
}
|
||||
|
||||
.progress-ring__prog {
|
||||
stroke: var(--arc-prog);
|
||||
fill: none;
|
||||
stroke-width: var(--arc-width);
|
||||
stroke-linecap: round;
|
||||
transition: stroke-dashoffset 0.35s;
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// CONTROLS
|
||||
// =============================================================================
|
||||
|
||||
.team-controls {
|
||||
margin-top: var(--space-2);
|
||||
display: flex;
|
||||
gap: var(--space-1);
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.team-nav {
|
||||
padding: var(--space-1);
|
||||
border: 1px solid var(--bs-border-color);
|
||||
background: var(--bs-body-bg);
|
||||
border-radius: 8px;
|
||||
|
||||
&:active:not(:disabled) {
|
||||
transform: scale(0.96);
|
||||
}
|
||||
|
||||
&:disabled {
|
||||
opacity: 0.4;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
}
|
||||
|
||||
.team-dots {
|
||||
display: flex;
|
||||
gap: 6px;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.team-dot {
|
||||
width: 6px;
|
||||
height: 6px;
|
||||
border-radius: 50%;
|
||||
background: color-mix(in srgb, var(--bs-secondary-color), transparent 70%);
|
||||
|
||||
&.active {
|
||||
background: var(--bs-primary);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,183 @@
|
||||
/**
|
||||
* _chat-date-groups.scss
|
||||
* Стили для группировки сообщений по датам
|
||||
* Основано на Telegram Web архитектуре
|
||||
*/
|
||||
|
||||
/* ─────────────────────────────────────────────────────────────
|
||||
MESSAGE DATE GROUP
|
||||
────────────────────────────────────────────────────────────── */
|
||||
.message-date-group {
|
||||
position: relative;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
/* ─────────────────────────────────────────────────────────────
|
||||
STICKY DATE HEADER
|
||||
────────────────────────────────────────────────────────────── */
|
||||
.sticky-date {
|
||||
position: sticky;
|
||||
top: 56px;
|
||||
z-index: 2;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 12px 16px;
|
||||
margin: 0;
|
||||
font-size: 0.875rem;
|
||||
font-weight: 500;
|
||||
color: var(--bs-secondary);
|
||||
text-align: center;
|
||||
background-color: transparent;
|
||||
pointer-events: none;
|
||||
transition: opacity 0.15s ease, background-color 0.2s ease;
|
||||
|
||||
span {
|
||||
display: inline-block;
|
||||
padding: 6px 12px;
|
||||
border-radius: 12px;
|
||||
background-color: var(--bs-light);
|
||||
pointer-events: all;
|
||||
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.05);
|
||||
transition: background-color 0.2s ease, box-shadow 0.2s ease, transform 0.15s ease;
|
||||
}
|
||||
|
||||
&.interactive span {
|
||||
cursor: pointer;
|
||||
user-select: none;
|
||||
|
||||
&:hover {
|
||||
background-color: var(--bs-primary);
|
||||
color: white;
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15);
|
||||
transform: translateY(-1px);
|
||||
}
|
||||
|
||||
&:active {
|
||||
transform: translateY(0);
|
||||
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
&:focus-visible {
|
||||
outline: 2px solid var(--bs-primary);
|
||||
outline-offset: 2px;
|
||||
}
|
||||
}
|
||||
|
||||
&.is-sticky span {
|
||||
background-color: rgba(255, 255, 255, 0.95);
|
||||
backdrop-filter: blur(10px);
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
}
|
||||
|
||||
/* ─────────────────────────────────────────────────────────────
|
||||
MESSAGES CONTAINER
|
||||
────────────────────────────────────────────────────────────── */
|
||||
.messages-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
width: 100%;
|
||||
gap: 0;
|
||||
}
|
||||
|
||||
/* ─────────────────────────────────────────────────────────────
|
||||
LOADING STATE
|
||||
────────────────────────────────────────────────────────────── */
|
||||
.message-date-group.loading .sticky-date span {
|
||||
animation: pulse 1.5s ease-in-out infinite;
|
||||
background: linear-gradient(
|
||||
90deg,
|
||||
var(--bs-light) 25%,
|
||||
rgba(0, 0, 0, 0.05) 50%,
|
||||
var(--bs-light) 75%
|
||||
);
|
||||
background-size: 200% 100%;
|
||||
}
|
||||
|
||||
@keyframes pulse {
|
||||
0%, 100% {
|
||||
background-position: 200% 0;
|
||||
}
|
||||
50% {
|
||||
background-position: 0 0;
|
||||
}
|
||||
}
|
||||
|
||||
/* ─────────────────────────────────────────────────────────────
|
||||
RESPONSIVE DESIGN
|
||||
────────────────────────────────────────────────────────────── */
|
||||
@media (max-width: 576px) {
|
||||
.sticky-date {
|
||||
top: 48px;
|
||||
padding: 10px 12px;
|
||||
font-size: 0.8125rem;
|
||||
|
||||
span {
|
||||
padding: 5px 10px;
|
||||
border-radius: 10px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@media (min-width: 577px) and (max-width: 992px) {
|
||||
.sticky-date {
|
||||
top: 52px;
|
||||
}
|
||||
}
|
||||
|
||||
/* ─────────────────────────────────────────────────────────────
|
||||
DARK MODE SUPPORT
|
||||
────────────────────────────────────────────────────────────── */
|
||||
@media (prefers-color-scheme: dark) {
|
||||
.sticky-date {
|
||||
color: var(--bs-light);
|
||||
|
||||
span {
|
||||
background-color: rgba(52, 58, 64, 0.95);
|
||||
color: var(--bs-light);
|
||||
}
|
||||
|
||||
&.interactive span:hover {
|
||||
background-color: var(--bs-primary);
|
||||
color: white;
|
||||
}
|
||||
|
||||
&.is-sticky span {
|
||||
background-color: rgba(52, 58, 64, 0.98);
|
||||
backdrop-filter: blur(10px);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* ─────────────────────────────────────────────────────────────
|
||||
ACCESSIBILITY
|
||||
────────────────────────────────────────────────────────────── */
|
||||
@media (prefers-contrast: high) {
|
||||
.sticky-date span {
|
||||
border: 1px solid currentColor;
|
||||
}
|
||||
}
|
||||
|
||||
@media (prefers-reduced-motion: reduce) {
|
||||
.sticky-date,
|
||||
.sticky-date span {
|
||||
transition: none;
|
||||
}
|
||||
}
|
||||
|
||||
/* ─────────────────────────────────────────────────────────────
|
||||
PRINT STYLES
|
||||
────────────────────────────────────────────────────────────── */
|
||||
@media print {
|
||||
.sticky-date {
|
||||
position: static;
|
||||
page-break-inside: avoid;
|
||||
|
||||
&.interactive span {
|
||||
cursor: default;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,901 @@
|
||||
/**
|
||||
* chat-detail.scss
|
||||
* Стили детального чата: scroll, bubbles, composer
|
||||
* Layout вынесен в layout/_chat-layout.scss
|
||||
*/
|
||||
|
||||
/* ─────────────────────────────────────────────────────────────
|
||||
CHAT PAGE CONTAINER
|
||||
────────────────────────────────────────────────────────────── */
|
||||
.chat-page {
|
||||
position: relative;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
// Учитываем высоту navbar сверху
|
||||
height: calc(100vh - var(--navbar-h, 60px));
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.chat-header {
|
||||
flex-shrink: 0;
|
||||
z-index: $z-dropdown - 1;
|
||||
background: var(--bs-body-bg);
|
||||
border-bottom: 1px solid var(--bs-border-color);
|
||||
padding: var(--space-3) var(--space-4);
|
||||
|
||||
&-content {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: var(--space-3);
|
||||
}
|
||||
|
||||
&-title {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: var(--space-3);
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
|
||||
h1 {
|
||||
font-size: 1.25rem;
|
||||
font-weight: 600;
|
||||
margin: 0;
|
||||
color: var(--bs-body-color);
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
i {
|
||||
font-size: 1.5rem;
|
||||
color: var(--bs-primary);
|
||||
flex-shrink: 0;
|
||||
}
|
||||
}
|
||||
|
||||
&-actions {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: var(--space-2);
|
||||
flex-shrink: 0;
|
||||
}
|
||||
}
|
||||
|
||||
// Mobile стили для header
|
||||
@media (max-width: 767.98px) {
|
||||
.chat-header {
|
||||
padding: var(--space-2) var(--space-3);
|
||||
|
||||
&-title {
|
||||
gap: var(--space-2);
|
||||
|
||||
h1 {
|
||||
font-size: 1.125rem;
|
||||
}
|
||||
|
||||
i {
|
||||
font-size: 1.25rem;
|
||||
}
|
||||
}
|
||||
|
||||
&-actions {
|
||||
gap: var(--space-1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* ─────────────────────────────────────────────────────────────
|
||||
CHAT CONTAINER (основной контейнер чата)
|
||||
────────────────────────────────────────────────────────────── */
|
||||
.chat-container {
|
||||
// Естественный размер контента, не растягиваем
|
||||
flex: 1 1 auto;
|
||||
|
||||
// Вертикальный flex для: alerts → messages → typing
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
// Критично для работы overflow в дочерних элементах
|
||||
min-height: 0;
|
||||
|
||||
// Предотвращает переполнение, scroll делегируется .chat-messages-scroll
|
||||
overflow: hidden;
|
||||
|
||||
// Для позиционирования scroll-to-bottom кнопки
|
||||
position: relative;
|
||||
|
||||
// Опциональный background для тестирования layout
|
||||
// background: var(--bs-body-bg);
|
||||
|
||||
// Alerts внутри контейнера не должны сжиматься
|
||||
> .alert {
|
||||
flex-shrink: 0;
|
||||
}
|
||||
}
|
||||
|
||||
/* ─────────────────────────────────────────────────────────────
|
||||
CHAT MESSAGES SCROLL (основной контейнер сообщений)
|
||||
────────────────────────────────────────────────────────────── */
|
||||
.chat-messages-scroll {
|
||||
flex: 1 0 0;
|
||||
overflow-y: auto;
|
||||
overflow-x: hidden;
|
||||
padding: var(--space-3);
|
||||
min-height: 0;
|
||||
scroll-behavior: smooth;
|
||||
-webkit-overflow-scrolling: touch;
|
||||
scrollbar-width: thin;
|
||||
scrollbar-gutter: stable;
|
||||
overscroll-behavior: contain;
|
||||
scrollbar-color: color-mix(in srgb, var(--bs-body-color), transparent 70%) transparent;
|
||||
|
||||
&:hover {
|
||||
scrollbar-color: color-mix(in srgb, var(--bs-body-color), transparent 55%) transparent;
|
||||
}
|
||||
|
||||
// Кастомный скроллбар для WebKit
|
||||
&::-webkit-scrollbar {
|
||||
width: 8px;
|
||||
}
|
||||
|
||||
&::-webkit-scrollbar-track {
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
&::-webkit-scrollbar-thumb {
|
||||
background-color: color-mix(in srgb, var(--bs-body-color), transparent 75%);
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
&:hover::-webkit-scrollbar-thumb {
|
||||
background-color: color-mix(in srgb, var(--bs-body-color), transparent 55%);
|
||||
}
|
||||
}
|
||||
|
||||
// Алиас для совместимости
|
||||
.chat-scroll {
|
||||
@extend .chat-messages-scroll;
|
||||
}
|
||||
|
||||
.chat-scroll {
|
||||
@include custom-scrollbar(color-mix(in srgb, var(--bs-body-color), transparent 75%));
|
||||
|
||||
&:hover {
|
||||
scrollbar-color: color-mix(in srgb, var(--bs-body-color), transparent 55%) transparent;
|
||||
}
|
||||
}
|
||||
|
||||
[data-bs-theme="dark"] .chat-scroll {
|
||||
scrollbar-color: color-mix(in srgb, var(--bs-body-color), transparent 78%) transparent;
|
||||
|
||||
&:hover {
|
||||
scrollbar-color: color-mix(in srgb, var(--bs-body-color), transparent 60%) transparent;
|
||||
}
|
||||
|
||||
&::-webkit-scrollbar-thumb {
|
||||
background-color: color-mix(in srgb, var(--bs-body-color), transparent 78%);
|
||||
}
|
||||
|
||||
&:hover::-webkit-scrollbar-thumb {
|
||||
background-color: color-mix(in srgb, var(--bs-body-color), transparent 60%);
|
||||
}
|
||||
}
|
||||
|
||||
@media (hover: none), (pointer: coarse) {
|
||||
.chat-scroll {
|
||||
scrollbar-width: none;
|
||||
}
|
||||
|
||||
.chat-scroll::-webkit-scrollbar {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
/* ─────────────────────────────────────────────────────────────
|
||||
CHAT COMPOSER WRAPPER & COMPOSER (Modern Messenger Style)
|
||||
────────────────────────────────────────────────────────────── */
|
||||
// TODO: Проблема с пустым пространством под композером на широких экранах
|
||||
// При изменении ширины окна высота navbar меняется, но calc(100vh - var(--navbar-h))
|
||||
// не всегда корректно работает. Нужно найти более надежное решение для прижатия
|
||||
// композера к низу страницы независимо от размера viewport.
|
||||
.chat-composer-wrapper {
|
||||
flex-shrink: 0;
|
||||
margin-top: auto; // Прижимаем к низу chat-page
|
||||
position: relative;
|
||||
z-index: $z-base;
|
||||
background: var(--bs-body-bg);
|
||||
border-top: 1px solid var(--bs-border-color);
|
||||
padding: var(--space-2);
|
||||
padding-bottom: calc(var(--space-2) + env(safe-area-inset-bottom));
|
||||
}
|
||||
|
||||
// Mobile стили для composer - фиксируем внизу экрана (замена bottom navbar)
|
||||
@media (max-width: 767.98px) {
|
||||
.chat-composer-wrapper {
|
||||
position: fixed;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
padding: var(--space-2) var(--space-2);
|
||||
padding-bottom: calc(var(--space-2) + env(safe-area-inset-bottom));
|
||||
border-top: 1px solid var(--bs-border-color);
|
||||
background: var(--bs-body-bg);
|
||||
z-index: 1020; // Над контентом, но под модалами
|
||||
}
|
||||
|
||||
// Уменьшенный padding для мобильных + компенсация высоты фиксированного composer
|
||||
.chat-messages-scroll {
|
||||
padding: var(--space-2);
|
||||
padding-bottom: calc(70px + env(safe-area-inset-bottom)); // Высота composer
|
||||
}
|
||||
}
|
||||
|
||||
.composer-wrap {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--space-1);
|
||||
}
|
||||
|
||||
.message-field {
|
||||
background: color-mix(in srgb, var(--bs-body-bg) 96%, transparent);
|
||||
border: 1px solid color-mix(in srgb, var(--bs-border-color), transparent 30%);
|
||||
}
|
||||
|
||||
.message-input {
|
||||
max-height: 180px;
|
||||
}
|
||||
|
||||
// Стили кнопок наследуются из custom-bootstrap.scss (glassmorphism)
|
||||
.btn-send {
|
||||
box-shadow: 0 4px 16px rgba(var(--bs-primary-rgb), 0.35);
|
||||
}
|
||||
|
||||
.message-emoji .dropdown-menu {
|
||||
background: var(--bs-body-bg);
|
||||
}
|
||||
|
||||
/* Превью вложений */
|
||||
.attachment-preview {
|
||||
padding: var(--space-1) 0;
|
||||
max-height: 200px;
|
||||
overflow-y: auto;
|
||||
max-width: 100%;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.attachment-item {
|
||||
background: var(--bs-secondary-bg);
|
||||
@include transition(background-color, $transition-fast);
|
||||
max-width: 100%; // Ограничиваем ширину контейнера
|
||||
}
|
||||
|
||||
/* Обрезка текста в форме загрузки файлов */
|
||||
.attachment-item .flex-grow-1 {
|
||||
min-width: 0; // Важно для flex-элементов с text-truncate внутри
|
||||
overflow: hidden; // Предотвращаем выход контента за границы
|
||||
}
|
||||
|
||||
.attachment-item .text-truncate {
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
/* Вложения в сообщениях */
|
||||
.bubble .attachment-item {
|
||||
background: transparent;
|
||||
padding: 0;
|
||||
margin-bottom: 8px;
|
||||
|
||||
// Медиа-элементы (изображения и видео) - минимальные отступы
|
||||
&.attachment-item--media {
|
||||
margin-bottom: 0;
|
||||
|
||||
a {
|
||||
display: block;
|
||||
padding: 0;
|
||||
line-height: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Стили только для ссылок на файлы (документы)
|
||||
.bubble .attachment-item:not(.attachment-item--media) a {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
padding: 0.5rem 0.75rem;
|
||||
border-radius: $radius-sm;
|
||||
text-decoration: none;
|
||||
border: none;
|
||||
@include transition-all($transition-fast);
|
||||
max-width: 100%;
|
||||
word-break: break-word;
|
||||
}
|
||||
|
||||
// Attachment links in regular messages (other person)
|
||||
.bubble-other .attachment-item a {
|
||||
background: rgba(0, 0, 0, 0.05);
|
||||
color: inherit;
|
||||
}
|
||||
|
||||
.bubble-other .attachment-item a:hover {
|
||||
background: rgba(0, 0, 0, 0.08);
|
||||
}
|
||||
|
||||
// Attachment links in own messages
|
||||
.bubble-me .attachment-item a {
|
||||
background: rgba(255, 255, 255, 0.15);
|
||||
color: rgba(255, 255, 255, 0.95);
|
||||
}
|
||||
|
||||
.bubble-me .attachment-item a:hover {
|
||||
background: rgba(255, 255, 255, 0.25);
|
||||
}
|
||||
|
||||
// Icons in attachments
|
||||
.bubble .attachment-item a i {
|
||||
font-size: 1.1rem;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.bubble-me .attachment-item a i {
|
||||
color: rgba(255, 255, 255, 0.9);
|
||||
}
|
||||
|
||||
// Text truncation for file names
|
||||
.bubble .attachment-item a span {
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
min-width: 0; // Important for flex items
|
||||
}
|
||||
|
||||
.message-attachments video,
|
||||
.message-attachments img,
|
||||
.message-attachments audio {
|
||||
max-width: 100%;
|
||||
width: auto;
|
||||
border-radius: $radius-sm;
|
||||
}
|
||||
|
||||
// TODO: Исследовать почему у медиа остаются большие отступы
|
||||
// Возможные причины: кэш браузера, конфликты с Bootstrap, line-height на родителях
|
||||
// Попробовать: добавить !important, проверить в DevTools computed styles
|
||||
// Убираем верхний отступ если вложение - только медиа без текста
|
||||
.message-attachments:has(.attachment-item--media):not(:has(.attachment-item:not(.attachment-item--media))) {
|
||||
margin-top: 0.25rem; // Уменьшенный отступ для медиа (вместо mt-2 = 0.5rem)
|
||||
}
|
||||
|
||||
.chat-media {
|
||||
display: block;
|
||||
width: auto;
|
||||
max-width: min(640px, 85vw);
|
||||
max-height: 520px;
|
||||
border-radius: $radius-md;
|
||||
background: rgba(0, 0, 0, 0.08);
|
||||
content-visibility: auto;
|
||||
vertical-align: top;
|
||||
}
|
||||
|
||||
// Telegram approach: изображения с aspect-ratio занимают полную ширину контейнера
|
||||
// Это позволяет браузеру резервировать место до загрузки → нет скачка скролла
|
||||
.chat-media--image[style*="aspect-ratio"] {
|
||||
width: 100% !important;
|
||||
height: auto !important;
|
||||
}
|
||||
|
||||
// Убираем этот конфликтующий селектор - заменяем на более специфичный
|
||||
.message-attachments .attachment-item:not(.attachment-item--media) > a,
|
||||
.message-attachments .attachment-item:not(.attachment-item--media) > video,
|
||||
.message-attachments .attachment-item:not(.attachment-item--media) > img {
|
||||
display: inline-flex;
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
.chat-media--video {
|
||||
background: #000;
|
||||
object-fit: contain;
|
||||
}
|
||||
|
||||
.chat-media--image {
|
||||
object-fit: cover;
|
||||
}
|
||||
|
||||
.message-pending .chat-media {
|
||||
min-width: 90px;
|
||||
min-height: 64px;
|
||||
}
|
||||
|
||||
.message-pending .bubble {
|
||||
position: relative;
|
||||
opacity: 0.8;
|
||||
}
|
||||
|
||||
.message-pending .message-status {
|
||||
color: rgba(255, 255, 255, 0.8);
|
||||
}
|
||||
|
||||
/* Состояния сообщений */
|
||||
.message-failed {
|
||||
opacity: 0.6;
|
||||
|
||||
.bubble {
|
||||
border: 1px solid var(--bs-danger);
|
||||
background: color-mix(in srgb, var(--bs-danger) 10%, transparent);
|
||||
}
|
||||
}
|
||||
|
||||
.message-highlighted {
|
||||
animation: messageHighlight 1s ease-in-out;
|
||||
|
||||
.bubble {
|
||||
background: color-mix(in srgb, var(--bs-warning) 20%, var(--bs-secondary-bg)) !important;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes messageHighlight {
|
||||
0%, 100% {
|
||||
transform: scale(1);
|
||||
}
|
||||
50% {
|
||||
transform: scale(1.02);
|
||||
}
|
||||
}
|
||||
|
||||
/* Режим выделения сообщений */
|
||||
.selection-mode {
|
||||
.msg {
|
||||
cursor: pointer;
|
||||
transition: background-color $transition-fast;
|
||||
|
||||
&:hover {
|
||||
background: var(--bs-tertiary-bg);
|
||||
}
|
||||
|
||||
&.selected {
|
||||
background: color-mix(in srgb, var(--bs-primary) 10%, transparent);
|
||||
}
|
||||
}
|
||||
|
||||
.msg-select-checkbox {
|
||||
position: absolute;
|
||||
left: var(--space-1);
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
border: 2px solid var(--bs-border-color);
|
||||
@include radius(50%);
|
||||
background: var(--bs-body-bg);
|
||||
cursor: pointer;
|
||||
transition: all $transition-fast;
|
||||
@include flex-center();
|
||||
z-index: 2;
|
||||
|
||||
&::after {
|
||||
content: '\f26b'; // Bootstrap icon checkmark
|
||||
font-family: 'bootstrap-icons';
|
||||
color: #fff;
|
||||
font-size: 12px;
|
||||
opacity: 0;
|
||||
transform: scale(0);
|
||||
transition: all $transition-fast;
|
||||
}
|
||||
|
||||
&.selected {
|
||||
background: var(--bs-primary);
|
||||
border-color: var(--bs-primary);
|
||||
|
||||
&::after {
|
||||
opacity: 1;
|
||||
transform: scale(1);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.bubble-other .message-status {
|
||||
color: rgba(0, 0, 0, 0.5);
|
||||
}
|
||||
|
||||
.message-attachments .text-secondary {
|
||||
color: inherit;
|
||||
opacity: 0.7;
|
||||
}
|
||||
|
||||
.bubble-me .message-attachments .text-secondary {
|
||||
color: rgba(255, 255, 255, 0.9) !important;
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.bubble-me .message-attachments .fw-semibold {
|
||||
color: rgba(255, 255, 255, 0.95) !important;
|
||||
}
|
||||
|
||||
.bubble-me .message-attachments i {
|
||||
color: rgba(255, 255, 255, 0.9) !important;
|
||||
}
|
||||
|
||||
.history-loader {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 0.5rem;
|
||||
padding: 0.75rem 0;
|
||||
color: var(--bs-secondary-color);
|
||||
}
|
||||
|
||||
.history-loader .spinner-border {
|
||||
width: 1rem;
|
||||
height: 1rem;
|
||||
}
|
||||
|
||||
/* Выпадающее меню вложений */
|
||||
.dropdown-menu {
|
||||
min-width: 200px;
|
||||
border-radius: $radius-md;
|
||||
@include shadow($shadow-md);
|
||||
padding: 8px;
|
||||
}
|
||||
|
||||
.dropdown-item {
|
||||
border-radius: $radius-sm;
|
||||
padding: 10px 12px;
|
||||
@include transition(background-color, $transition-fast);
|
||||
}
|
||||
|
||||
.dropdown-item:hover {
|
||||
background: var(--bs-secondary-bg);
|
||||
}
|
||||
|
||||
.dropdown-item i {
|
||||
width: 24px;
|
||||
}
|
||||
|
||||
.composer-tools {
|
||||
display: flex;
|
||||
gap: var(--space-1);
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
/* ─────────────────────────────────────────────────────────────
|
||||
TYPING INDICATOR
|
||||
────────────────────────────────────────────────────────────── */
|
||||
.chat-typing-indicator {
|
||||
padding: var(--space-2) var(--space-3);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: var(--space-2);
|
||||
font-size: 0.875rem;
|
||||
color: var(--bs-secondary-color);
|
||||
font-style: italic;
|
||||
background: var(--bs-body-bg);
|
||||
border-top: 1px solid var(--bs-border-color);
|
||||
flex-shrink: 0;
|
||||
|
||||
&.d-none {
|
||||
display: none !important;
|
||||
}
|
||||
}
|
||||
|
||||
.typing-dots {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
|
||||
span {
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
border-radius: 50%;
|
||||
background: var(--bs-secondary-color);
|
||||
opacity: 0.6;
|
||||
animation: typingBounce 1.4s infinite both;
|
||||
|
||||
&:nth-child(2) {
|
||||
animation-delay: 0.2s;
|
||||
}
|
||||
|
||||
&:nth-child(3) {
|
||||
animation-delay: 0.4s;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes typingBounce {
|
||||
0%, 60%, 100% {
|
||||
transform: translateY(0);
|
||||
}
|
||||
30% {
|
||||
transform: translateY(-8px);
|
||||
}
|
||||
}
|
||||
|
||||
.typing-text {
|
||||
font-size: 0.875rem;
|
||||
color: var(--bs-secondary-color);
|
||||
}
|
||||
|
||||
/* ─────────────────────────────────────────────────────────────
|
||||
HISTORY LOADER
|
||||
────────────────────────────────────────────────────────────── */
|
||||
.history-loader {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: var(--space-2);
|
||||
padding: var(--space-3) 0;
|
||||
color: var(--bs-secondary-color);
|
||||
font-size: 0.875rem;
|
||||
|
||||
.spinner-border {
|
||||
width: 1rem;
|
||||
height: 1rem;
|
||||
border-width: 2px;
|
||||
}
|
||||
|
||||
&.d-none {
|
||||
display: none !important;
|
||||
}
|
||||
}
|
||||
|
||||
.initial-loader {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: var(--space-5) 0;
|
||||
color: var(--bs-secondary-color);
|
||||
|
||||
.spinner-border {
|
||||
width: 3rem;
|
||||
height: 3rem;
|
||||
}
|
||||
|
||||
p {
|
||||
margin-top: var(--space-3);
|
||||
font-size: 1rem;
|
||||
}
|
||||
}
|
||||
|
||||
/* ─────────────────────────────────────────────────────────────
|
||||
SCROLL TO BOTTOM BUTTON
|
||||
────────────────────────────────────────────────────────────── */
|
||||
.scroll-to-bottom {
|
||||
position: fixed;
|
||||
right: var(--space-3);
|
||||
bottom: calc(var(--space-3) + 80px);
|
||||
z-index: $z-dropdown;
|
||||
display: none;
|
||||
width: 44px;
|
||||
height: 44px;
|
||||
@include circle();
|
||||
padding: 0 !important;
|
||||
border: none !important;
|
||||
background: var(--bs-primary) !important;
|
||||
color: #fff !important;
|
||||
@include shadow($shadow-lg, rgba(var(--bs-primary-rgb), 0.25));
|
||||
@include transition((transform, box-shadow), $transition-fast);
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
opacity: 0;
|
||||
pointer-events: none;
|
||||
|
||||
i {
|
||||
font-size: 1.25rem;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
transform: scale(1.08);
|
||||
box-shadow: 0 6px 20px rgba(var(--bs-primary-rgb), 0.35) !important;
|
||||
}
|
||||
|
||||
&:active {
|
||||
transform: scale(0.95);
|
||||
}
|
||||
}
|
||||
|
||||
.scroll-to-bottom.show {
|
||||
display: inline-flex;
|
||||
opacity: 1;
|
||||
pointer-events: auto;
|
||||
animation: fadeInUp 0.3s ease;
|
||||
}
|
||||
|
||||
/* Badge непрочитанных на кнопке скролла */
|
||||
.scroll-to-bottom {
|
||||
position: relative; // Для позиционирования badge
|
||||
|
||||
.unread-badge {
|
||||
position: absolute;
|
||||
top: -6px;
|
||||
right: -6px;
|
||||
min-width: 20px;
|
||||
height: 20px;
|
||||
padding: 0 6px;
|
||||
@include flex-center();
|
||||
@include radius($badge-radius);
|
||||
background: var(--bs-danger);
|
||||
color: #fff;
|
||||
font-size: 11px;
|
||||
font-weight: 600;
|
||||
line-height: 1;
|
||||
@include shadow($shadow-md);
|
||||
transition: opacity $transition-fast, transform $transition-fast;
|
||||
z-index: 1;
|
||||
|
||||
// Скрытое состояние
|
||||
&.badge-hidden {
|
||||
opacity: 0;
|
||||
transform: scale(0.5);
|
||||
pointer-events: none;
|
||||
}
|
||||
}
|
||||
|
||||
// Когда есть непрочитанные
|
||||
&.has-unread {
|
||||
.unread-badge {
|
||||
opacity: 1;
|
||||
transform: scale(1);
|
||||
animation: badgePulse 2s ease-in-out infinite;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes badgePulse {
|
||||
0%, 100% {
|
||||
box-shadow: 0 2px 8px rgba(var(--bs-danger-rgb), 0.3);
|
||||
}
|
||||
50% {
|
||||
box-shadow: 0 2px 12px rgba(var(--bs-danger-rgb), 0.5);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes fadeInUp {
|
||||
from {
|
||||
opacity: 0;
|
||||
transform: translateY(10px);
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
}
|
||||
}
|
||||
|
||||
// Mobile стили для scroll button
|
||||
@media (max-width: 767.98px) {
|
||||
.scroll-to-bottom {
|
||||
right: var(--space-2);
|
||||
bottom: calc(var(--space-2) + 70px); // Учитываем composer на мобилке
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
|
||||
i {
|
||||
font-size: 1.125rem;
|
||||
}
|
||||
|
||||
.unread-badge {
|
||||
top: -4px;
|
||||
right: -4px;
|
||||
min-width: 18px;
|
||||
height: 18px;
|
||||
font-size: 10px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* ─────────────────────────────────────────────────────────────
|
||||
MESSAGES LAYOUT
|
||||
────────────────────────────────────────────────────────────── */
|
||||
// Выравнивание аватара по низу сообщения
|
||||
.d-flex.msg {
|
||||
align-items: flex-end;
|
||||
}
|
||||
|
||||
/* ─────────────────────────────────────────────────────────────
|
||||
BUBBLES & AVATARS
|
||||
────────────────────────────────────────────────────────────── */
|
||||
.bubble {
|
||||
border-radius: $radius-lg;
|
||||
padding: var(--space-1) var(--space-2);
|
||||
word-break: break-word;
|
||||
white-space: pre-line; /* Сохраняет \n, но схлопывает пробелы из HTML */
|
||||
max-width: 70%; /* Ограничиваем ширину bubble */
|
||||
@include shadow($shadow-xs);
|
||||
user-select: text; /* Разрешаем выделение текста */
|
||||
cursor: text; /* Курсор текстового выделения */
|
||||
|
||||
// Наследование white-space для вложенных элементов
|
||||
> div {
|
||||
white-space: inherit;
|
||||
}
|
||||
|
||||
// Специально для голосований - схлопываем whitespace
|
||||
.poll-widget,
|
||||
.poll-question,
|
||||
.poll-options,
|
||||
.poll-option,
|
||||
.poll-footer {
|
||||
white-space: normal;
|
||||
}
|
||||
}
|
||||
|
||||
.bubble-me {
|
||||
background: color-mix(in srgb, var(--bs-primary) 92%, #fff 0%);
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.bubble-other {
|
||||
background: var(--bs-secondary-bg);
|
||||
color: var(--bs-body-color);
|
||||
}
|
||||
|
||||
.mini-ava {
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
flex-shrink: 0; /* Не сжимать аватар */
|
||||
border-radius: 50%;
|
||||
overflow: hidden;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background: var(--bs-tertiary-bg);
|
||||
border: 1px solid var(--bs-border-color);
|
||||
|
||||
img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: cover; /* Кадрируем без деформации */
|
||||
display: block;
|
||||
}
|
||||
|
||||
i {
|
||||
font-size: 20px;
|
||||
color: var(--bs-secondary-color);
|
||||
}
|
||||
}
|
||||
|
||||
/* ─────────────────────────────────────────────────────────────
|
||||
DIVIDERS (день / непрочитанные)
|
||||
────────────────────────────────────────────────────────────── */
|
||||
.day-divider,
|
||||
.unread-divider {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: var(--space-1);
|
||||
margin: var(--space-1) 0 var(--space-2);
|
||||
font-size: 0.85rem;
|
||||
line-height: 1;
|
||||
font-weight: 700;
|
||||
pointer-events: none;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.day-divider::before,
|
||||
.day-divider::after,
|
||||
.unread-divider::before,
|
||||
.unread-divider::after {
|
||||
content: "";
|
||||
flex: 1 1 auto;
|
||||
height: 1px;
|
||||
background: color-mix(in srgb, var(--bs-border-color), transparent 30%);
|
||||
}
|
||||
|
||||
.day-divider > span,
|
||||
.unread-divider > span {
|
||||
flex: 0 0 auto;
|
||||
padding: 0 var(--space-1);
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
.day-divider {
|
||||
color: var(--bs-secondary-color);
|
||||
}
|
||||
|
||||
.unread-divider {
|
||||
color: var(--bs-danger);
|
||||
}
|
||||
@ -0,0 +1,298 @@
|
||||
/**
|
||||
* _chat-enhanced.scss
|
||||
* Enhanced chat features: replies, forwards, reactions, attachments
|
||||
*/
|
||||
|
||||
/* ─────────────────────────────────────────────────────────────
|
||||
REPLY PREVIEW
|
||||
────────────────────────────────────────────────────────────── */
|
||||
.reply-preview {
|
||||
font-size: 0.85rem;
|
||||
padding: 4px 8px;
|
||||
background: rgba(0, 0, 0, 0.05);
|
||||
border-left: 3px solid var(--bs-primary);
|
||||
border-radius: $radius-xs;
|
||||
|
||||
i {
|
||||
color: var(--bs-primary);
|
||||
}
|
||||
}
|
||||
|
||||
/* ─────────────────────────────────────────────────────────────
|
||||
FORWARD INFO
|
||||
────────────────────────────────────────────────────────────── */
|
||||
.forward-info {
|
||||
font-size: 0.85rem;
|
||||
color: var(--bs-secondary);
|
||||
font-style: italic;
|
||||
|
||||
i {
|
||||
color: var(--bs-info);
|
||||
}
|
||||
}
|
||||
|
||||
/* ─────────────────────────────────────────────────────────────
|
||||
MESSAGE ACTIONS (shown on hover)
|
||||
────────────────────────────────────────────────────────────── */
|
||||
.msg {
|
||||
position: relative;
|
||||
|
||||
&:hover .message-actions {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
&:hover .quick-reactions {
|
||||
opacity: 1;
|
||||
pointer-events: all;
|
||||
}
|
||||
|
||||
&.new {
|
||||
animation: slideIn 0.3s ease-out;
|
||||
}
|
||||
|
||||
&.system-message {
|
||||
justify-content: center !important;
|
||||
|
||||
.bubble {
|
||||
background: #f8f9fa;
|
||||
border: 1px dashed #dee2e6;
|
||||
color: #6c757d;
|
||||
text-align: center;
|
||||
font-size: 0.9rem;
|
||||
font-style: italic;
|
||||
}
|
||||
}
|
||||
|
||||
.bubble {
|
||||
em.text-muted {
|
||||
font-size: 0.9rem;
|
||||
opacity: 0.7;
|
||||
}
|
||||
|
||||
.bi-pencil-fill {
|
||||
font-size: 0.7rem;
|
||||
opacity: 0.6;
|
||||
}
|
||||
|
||||
.bi-pin-angle-fill {
|
||||
color: var(--bs-warning);
|
||||
font-size: 1rem;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.message-actions {
|
||||
position: absolute;
|
||||
top: -12px;
|
||||
right: 10px;
|
||||
background: white;
|
||||
border: 1px solid #dee2e6;
|
||||
border-radius: $radius-pill;
|
||||
padding: 2px 4px;
|
||||
@include shadow($shadow-sm);
|
||||
opacity: 0;
|
||||
@include transition(opacity, $transition-fast);
|
||||
z-index: 10;
|
||||
|
||||
.btn {
|
||||
padding: 2px 6px;
|
||||
font-size: 0.85rem;
|
||||
}
|
||||
}
|
||||
|
||||
/* ─────────────────────────────────────────────────────────────
|
||||
REACTIONS
|
||||
────────────────────────────────────────────────────────────── */
|
||||
.reactions {
|
||||
display: flex;
|
||||
gap: 4px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.reaction-btn {
|
||||
border: 1px solid #dee2e6;
|
||||
background: white;
|
||||
border-radius: $radius-md;
|
||||
padding: 2px 8px;
|
||||
font-size: 0.9rem;
|
||||
cursor: pointer;
|
||||
@include transition-all($transition-fast);
|
||||
|
||||
&:hover {
|
||||
background: #f8f9fa;
|
||||
transform: scale(1.1);
|
||||
}
|
||||
|
||||
&.active {
|
||||
background: #e3f2fd;
|
||||
border-color: var(--bs-primary);
|
||||
}
|
||||
|
||||
.count {
|
||||
font-size: 0.75rem;
|
||||
color: #666;
|
||||
margin-left: 2px;
|
||||
}
|
||||
}
|
||||
|
||||
/* ─────────────────────────────────────────────────────────────
|
||||
QUICK REACTIONS (on hover)
|
||||
────────────────────────────────────────────────────────────── */
|
||||
.quick-reactions {
|
||||
position: absolute;
|
||||
bottom: -20px;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
background: white;
|
||||
border: 1px solid #dee2e6;
|
||||
border-radius: $radius-pill;
|
||||
padding: 4px 8px;
|
||||
@include shadow($shadow-md);
|
||||
opacity: 0;
|
||||
pointer-events: none;
|
||||
@include transition(opacity, $transition-fast);
|
||||
z-index: 20;
|
||||
display: flex;
|
||||
gap: 4px;
|
||||
|
||||
button {
|
||||
border: none;
|
||||
background: transparent;
|
||||
font-size: 1.1rem;
|
||||
padding: 4px 8px;
|
||||
cursor: pointer;
|
||||
border-radius: $radius-sm;
|
||||
@include transition(background, $transition-fast);
|
||||
|
||||
&:hover {
|
||||
background: #f0f0f0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* ─────────────────────────────────────────────────────────────
|
||||
ATTACHMENTS
|
||||
────────────────────────────────────────────────────────────── */
|
||||
.attachments {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.attachment-image {
|
||||
max-width: 200px;
|
||||
max-height: 200px;
|
||||
border-radius: $radius-sm;
|
||||
cursor: pointer;
|
||||
@include transition(transform, $transition-fast);
|
||||
|
||||
&:hover {
|
||||
transform: scale(1.05);
|
||||
}
|
||||
}
|
||||
|
||||
.attachment-file {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
padding: 6px 12px;
|
||||
background: rgba(0, 0, 0, 0.05);
|
||||
border-radius: $radius-sm;
|
||||
text-decoration: none;
|
||||
color: inherit;
|
||||
font-size: 0.9rem;
|
||||
|
||||
&:hover {
|
||||
background: rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
}
|
||||
|
||||
/* ─────────────────────────────────────────────────────────────
|
||||
CHAT LIST ITEMS
|
||||
────────────────────────────────────────────────────────────── */
|
||||
.chat-list-item {
|
||||
position: relative;
|
||||
|
||||
&.pinned {
|
||||
background: #fffbeb;
|
||||
border-left: 4px solid var(--bs-warning);
|
||||
}
|
||||
}
|
||||
|
||||
/* ─────────────────────────────────────────────────────────────
|
||||
BADGES & INDICATORS
|
||||
────────────────────────────────────────────────────────────── */
|
||||
.mention-badge {
|
||||
background: #ef4444;
|
||||
color: white;
|
||||
border-radius: 12px;
|
||||
padding: 2px 8px;
|
||||
font-size: 0.75rem;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.thread-indicator {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
font-size: 0.85rem;
|
||||
color: var(--bs-primary);
|
||||
cursor: pointer;
|
||||
|
||||
&:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
}
|
||||
|
||||
/* ─────────────────────────────────────────────────────────────
|
||||
INDICATORS
|
||||
────────────────────────────────────────────────────────────── */
|
||||
#typing {
|
||||
padding: 4px 12px;
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
#editing-indicator,
|
||||
#reply-indicator {
|
||||
margin-bottom: 8px;
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
/* ─────────────────────────────────────────────────────────────
|
||||
ANIMATIONS
|
||||
────────────────────────────────────────────────────────────── */
|
||||
@keyframes slideIn {
|
||||
from {
|
||||
opacity: 0;
|
||||
transform: translateY(20px);
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
}
|
||||
}
|
||||
|
||||
/* ─────────────────────────────────────────────────────────────
|
||||
MOBILE RESPONSIVE
|
||||
────────────────────────────────────────────────────────────── */
|
||||
@media (max-width: 768px) {
|
||||
.message-actions {
|
||||
position: static;
|
||||
opacity: 1;
|
||||
margin-top: 8px;
|
||||
}
|
||||
|
||||
.quick-reactions {
|
||||
position: static;
|
||||
transform: none;
|
||||
opacity: 1;
|
||||
pointer-events: all;
|
||||
margin-top: 8px;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.attachment-image {
|
||||
max-width: 150px;
|
||||
max-height: 150px;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,320 @@
|
||||
/**
|
||||
* _chat-list-enhanced.scss
|
||||
* Enhanced chat list with context menus, animations, avatars
|
||||
*/
|
||||
|
||||
/* ─────────────────────────────────────────────────────────────
|
||||
CONTEXT MENU
|
||||
────────────────────────────────────────────────────────────── */
|
||||
.context-menu {
|
||||
position: absolute;
|
||||
background: white;
|
||||
border: 1px solid #dee2e6;
|
||||
border-radius: $radius-sm;
|
||||
@include shadow($shadow-md);
|
||||
padding: 4px 0;
|
||||
min-width: 200px;
|
||||
z-index: 9999;
|
||||
|
||||
button {
|
||||
width: 100%;
|
||||
border: none;
|
||||
background: transparent;
|
||||
padding: 8px 16px;
|
||||
text-align: left;
|
||||
cursor: pointer;
|
||||
font-size: 0.9rem;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
|
||||
&:hover {
|
||||
background: #f8f9fa;
|
||||
}
|
||||
}
|
||||
|
||||
hr {
|
||||
margin: 4px 0;
|
||||
border-color: #dee2e6;
|
||||
}
|
||||
}
|
||||
|
||||
/* ─────────────────────────────────────────────────────────────
|
||||
CHAT ROW
|
||||
────────────────────────────────────────────────────────────── */
|
||||
.chat-row {
|
||||
border-top: 1px solid var(--bs-border-color);
|
||||
overflow: hidden;
|
||||
min-height: 60px;
|
||||
|
||||
&.updated {
|
||||
background: #e3f2fd;
|
||||
@include transition(background, $transition-medium);
|
||||
}
|
||||
|
||||
&.pinned {
|
||||
background: #fffbeb;
|
||||
border-left: 4px solid var(--bs-warning);
|
||||
}
|
||||
|
||||
.flex-grow-1 {
|
||||
min-width: 0;
|
||||
overflow: hidden;
|
||||
|
||||
a,
|
||||
.card-title {
|
||||
display: block;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
max-width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
[data-last-preview],
|
||||
.card-subtitle {
|
||||
display: block;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
&:hover .chat-actions-menu .btn {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
/* ─────────────────────────────────────────────────────────────
|
||||
CHAT SECTION
|
||||
────────────────────────────────────────────────────────────── */
|
||||
.chat-section {
|
||||
overflow: visible !important;
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
|
||||
&:has(.dropdown-menu.show) {
|
||||
z-index: 1000;
|
||||
}
|
||||
|
||||
.card-header {
|
||||
text-align: center;
|
||||
border-bottom: 1px solid var(--bs-border-color);
|
||||
|
||||
.card-icon {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.card-meta {
|
||||
width: 100%;
|
||||
text-align: center;
|
||||
}
|
||||
}
|
||||
|
||||
.list-chats {
|
||||
overflow: visible !important;
|
||||
}
|
||||
}
|
||||
|
||||
/* ─────────────────────────────────────────────────────────────
|
||||
CHAT AVATARS - КРУГЛЫЕ!
|
||||
────────────────────────────────────────────────────────────── */
|
||||
.chat-avatar,
|
||||
.card-icon img,
|
||||
.chat-list-wrapper .card-icon,
|
||||
.chat-row .card-icon,
|
||||
.chat-section .card-icon {
|
||||
border-radius: 50% !important;
|
||||
}
|
||||
|
||||
.chat-avatar,
|
||||
.card-icon img {
|
||||
width: 44px !important;
|
||||
height: 44px !important;
|
||||
object-fit: cover;
|
||||
border: 2px solid var(--bs-border-color);
|
||||
}
|
||||
|
||||
.chat-list-wrapper .card-icon,
|
||||
.chat-row .card-icon {
|
||||
width: 44px !important;
|
||||
height: 44px !important;
|
||||
min-width: 44px;
|
||||
min-height: 44px;
|
||||
border-radius: 50% !important;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.chat-avatar-group {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
img {
|
||||
margin-left: -8px;
|
||||
border: 2px solid white;
|
||||
border-radius: 50% !important;
|
||||
|
||||
&:first-child {
|
||||
margin-left: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* ─────────────────────────────────────────────────────────────
|
||||
CHAT ACTIONS MENU
|
||||
────────────────────────────────────────────────────────────── */
|
||||
.chat-actions-menu {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex-shrink: 0;
|
||||
|
||||
.btn {
|
||||
opacity: 0;
|
||||
@include transition(opacity, $transition-fast);
|
||||
|
||||
&:focus,
|
||||
&[aria-expanded="true"] {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
.dropdown-menu {
|
||||
font-size: 0.9rem;
|
||||
min-width: 200px;
|
||||
z-index: 9999;
|
||||
position: fixed !important;
|
||||
}
|
||||
|
||||
.dropdown-item {
|
||||
padding: 0.5rem 1rem;
|
||||
|
||||
i {
|
||||
width: 20px;
|
||||
text-align: center;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* ─────────────────────────────────────────────────────────────
|
||||
CHAT TYPE SIDEBAR
|
||||
────────────────────────────────────────────────────────────── */
|
||||
:root {
|
||||
--chat-sidebar-offset: 120px;
|
||||
}
|
||||
|
||||
.chat-type-sidebar {
|
||||
flex-shrink: 0;
|
||||
align-self: flex-start;
|
||||
position: sticky;
|
||||
top: calc(var(--navbar-h, 60px) + var(--chat-sidebar-offset, 120px));
|
||||
|
||||
.card {
|
||||
max-height: calc(100vh - var(--navbar-h, 60px) - var(--chat-sidebar-offset, 120px) - 24px);
|
||||
overflow-y: auto;
|
||||
}
|
||||
}
|
||||
|
||||
.chat-type-btn {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 0.35rem;
|
||||
position: relative;
|
||||
cursor: pointer;
|
||||
@include transition((background-color, opacity), $transition-fast);
|
||||
|
||||
&[style*="display: none"] {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
background-color: var(--bs-light) !important;
|
||||
}
|
||||
|
||||
&.active {
|
||||
background-color: var(--bs-primary) !important;
|
||||
color: white !important;
|
||||
|
||||
i {
|
||||
color: white;
|
||||
}
|
||||
|
||||
.badge {
|
||||
background-color: rgba(255, 255, 255, 0.3) !important;
|
||||
color: white !important;
|
||||
border-color: white !important;
|
||||
}
|
||||
}
|
||||
|
||||
.badge {
|
||||
position: absolute;
|
||||
top: 0.25rem;
|
||||
right: 0.25rem;
|
||||
min-width: 1.25rem;
|
||||
height: 1.25rem;
|
||||
padding: 0.15rem 0.3rem;
|
||||
border-radius: 50%;
|
||||
font-size: 0.65rem;
|
||||
line-height: 1;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
}
|
||||
|
||||
/* ─────────────────────────────────────────────────────────────
|
||||
MOBILE VERSION
|
||||
────────────────────────────────────────────────────────────── */
|
||||
@media (max-width: 768px) {
|
||||
.chat-list-wrapper {
|
||||
flex-direction: column !important;
|
||||
}
|
||||
|
||||
.chat-type-sidebar {
|
||||
width: 100% !important;
|
||||
order: -1;
|
||||
position: static;
|
||||
|
||||
.card {
|
||||
max-height: none;
|
||||
overflow: hidden;
|
||||
padding: 0.25rem 0.5rem;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.list-group {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
flex-wrap: nowrap;
|
||||
gap: 0.5rem;
|
||||
width: 100%;
|
||||
overflow-x: auto;
|
||||
overflow-y: hidden;
|
||||
padding: 0 0.25rem 0.25rem;
|
||||
scrollbar-width: thin;
|
||||
scroll-snap-type: x proximity;
|
||||
-webkit-overflow-scrolling: touch;
|
||||
}
|
||||
}
|
||||
|
||||
.chat-type-btn {
|
||||
flex: 0 0 auto;
|
||||
min-width: 60px;
|
||||
max-width: 80px;
|
||||
padding: 0.5rem 0.25rem !important;
|
||||
border-radius: 0.75rem !important;
|
||||
border: 1px solid var(--bs-border-color) !important;
|
||||
scroll-snap-align: start;
|
||||
|
||||
i {
|
||||
font-size: 1.25rem !important;
|
||||
}
|
||||
|
||||
.badge {
|
||||
font-size: 0.6rem;
|
||||
min-width: 1.1rem;
|
||||
height: 1.1rem;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,149 @@
|
||||
/**
|
||||
* _chat-new-messages-indicator.scss
|
||||
* Индикатор новых сообщений в чате
|
||||
* Кнопка для прокрутки к новым сообщениям
|
||||
*/
|
||||
|
||||
/* ─────────────────────────────────────────────────────────────
|
||||
NEW MESSAGES INDICATOR
|
||||
────────────────────────────────────────────────────────────── */
|
||||
.new-messages-indicator {
|
||||
position: absolute;
|
||||
bottom: 20px;
|
||||
right: 20px;
|
||||
z-index: 1000;
|
||||
padding: 10px 16px;
|
||||
min-width: 180px;
|
||||
background: var(--bs-primary, #0d6efd);
|
||||
color: white;
|
||||
border: none;
|
||||
border-radius: 24px;
|
||||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
opacity: 0;
|
||||
transform: translateY(10px);
|
||||
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
||||
pointer-events: none;
|
||||
display: none;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 8px;
|
||||
cursor: pointer;
|
||||
|
||||
&.show {
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
pointer-events: all;
|
||||
animation: pulse 0.6s ease-in-out;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
background: var(--bs-primary-dark, #0a58ca);
|
||||
box-shadow: 0 6px 16px rgba(0, 0, 0, 0.2);
|
||||
transform: translateY(-2px);
|
||||
}
|
||||
|
||||
&:active {
|
||||
transform: translateY(0) scale(0.98);
|
||||
}
|
||||
|
||||
svg {
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.badge {
|
||||
display: inline-block;
|
||||
min-width: 20px;
|
||||
padding: 2px 6px;
|
||||
font-size: 12px;
|
||||
font-weight: 600;
|
||||
line-height: 1;
|
||||
text-align: center;
|
||||
white-space: nowrap;
|
||||
vertical-align: baseline;
|
||||
border-radius: 10px;
|
||||
background-color: rgba(255, 255, 255, 0.3);
|
||||
color: white;
|
||||
margin: 0 4px;
|
||||
}
|
||||
|
||||
/* Варианты расположения */
|
||||
&.left {
|
||||
right: auto;
|
||||
left: 20px;
|
||||
}
|
||||
|
||||
&.center {
|
||||
left: 50%;
|
||||
right: auto;
|
||||
transform: translateX(-50%) translateY(10px);
|
||||
|
||||
&.show {
|
||||
transform: translateX(-50%) translateY(0);
|
||||
}
|
||||
|
||||
&:hover {
|
||||
transform: translateX(-50%) translateY(-2px);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* ─────────────────────────────────────────────────────────────
|
||||
CHAT SCROLL CONTAINER
|
||||
────────────────────────────────────────────────────────────── */
|
||||
#chatScroll {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
/* ─────────────────────────────────────────────────────────────
|
||||
ANIMATIONS
|
||||
────────────────────────────────────────────────────────────── */
|
||||
@keyframes pulse {
|
||||
0%, 100% {
|
||||
transform: scale(1);
|
||||
}
|
||||
50% {
|
||||
transform: scale(1.05);
|
||||
}
|
||||
}
|
||||
|
||||
/* ─────────────────────────────────────────────────────────────
|
||||
RESPONSIVE
|
||||
────────────────────────────────────────────────────────────── */
|
||||
@media (max-width: 768px) {
|
||||
.new-messages-indicator {
|
||||
bottom: 15px;
|
||||
right: 15px;
|
||||
min-width: 160px;
|
||||
padding: 8px 14px;
|
||||
font-size: 13px;
|
||||
|
||||
svg {
|
||||
width: 18px;
|
||||
height: 18px;
|
||||
}
|
||||
|
||||
.badge {
|
||||
font-size: 11px;
|
||||
min-width: 18px;
|
||||
padding: 2px 5px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* ─────────────────────────────────────────────────────────────
|
||||
DARK THEME
|
||||
────────────────────────────────────────────────────────────── */
|
||||
@media (prefers-color-scheme: dark) {
|
||||
.new-messages-indicator {
|
||||
background: #1a73e8;
|
||||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3);
|
||||
|
||||
&:hover {
|
||||
background: #1557b0;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,187 @@
|
||||
/**
|
||||
* _chat-polls.scss
|
||||
* Стили для голосований в чате
|
||||
*/
|
||||
|
||||
/* ─────────────────────────────────────────────────────────────
|
||||
POLL MODAL
|
||||
────────────────────────────────────────────────────────────── */
|
||||
#pollModal .modal-dialog {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
min-height: calc(100% - 1rem);
|
||||
|
||||
@media (min-width: 576px) {
|
||||
min-height: calc(100% - 3.5rem);
|
||||
max-width: 500px;
|
||||
margin: 1.75rem auto;
|
||||
}
|
||||
}
|
||||
|
||||
#pollModal .remove-option {
|
||||
border-top-left-radius: 0;
|
||||
border-bottom-left-radius: 0;
|
||||
}
|
||||
|
||||
#pollOptions .input-group-text {
|
||||
min-width: 40px;
|
||||
justify-content: center;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
/* ─────────────────────────────────────────────────────────────
|
||||
POLL WIDGET
|
||||
────────────────────────────────────────────────────────────── */
|
||||
.poll-widget {
|
||||
padding: 1rem;
|
||||
background: var(--bs-secondary-bg);
|
||||
border-radius: $radius-sm;
|
||||
margin: 0.5rem 0;
|
||||
}
|
||||
|
||||
.poll-question {
|
||||
font-size: 1.1rem;
|
||||
color: var(--bs-body-color);
|
||||
font-weight: 400;
|
||||
}
|
||||
|
||||
.poll-option {
|
||||
position: relative;
|
||||
|
||||
&.voted {
|
||||
background: rgba(var(--bs-success-rgb), 0.1);
|
||||
padding: 0.5rem;
|
||||
border-radius: $radius-xs;
|
||||
|
||||
.progress-bar {
|
||||
background-color: var(--bs-success);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.btn-poll-option {
|
||||
@include transition-all($transition-fast);
|
||||
|
||||
&:hover {
|
||||
background: var(--bs-primary);
|
||||
color: white;
|
||||
border-color: var(--bs-primary);
|
||||
transform: translateX(4px);
|
||||
}
|
||||
}
|
||||
|
||||
.poll-option-result {
|
||||
.progress {
|
||||
background-color: rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.progress-bar {
|
||||
background-color: var(--bs-primary);
|
||||
@include transition(width, $transition-slow);
|
||||
}
|
||||
}
|
||||
|
||||
.poll-footer {
|
||||
border-top: 1px solid rgba(0, 0, 0, 0.1);
|
||||
padding-top: 0.75rem;
|
||||
}
|
||||
|
||||
/* ─────────────────────────────────────────────────────────────
|
||||
POLL IN MESSAGE BUBBLES
|
||||
────────────────────────────────────────────────────────────── */
|
||||
.message-bubble .poll-widget {
|
||||
max-width: 500px;
|
||||
}
|
||||
|
||||
/* Голосования в сообщениях других пользователей */
|
||||
.bubble-other .poll-widget {
|
||||
background: var(--bs-secondary-bg);
|
||||
}
|
||||
|
||||
/* Голосования в своих сообщениях */
|
||||
.bubble-me {
|
||||
.poll-widget {
|
||||
background: rgba(255, 255, 255, 0.15);
|
||||
}
|
||||
|
||||
.poll-question {
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.poll-option-result {
|
||||
.progress {
|
||||
background-color: rgba(255, 255, 255, 0.2);
|
||||
}
|
||||
|
||||
.progress-bar {
|
||||
background-color: rgba(255, 255, 255, 0.6);
|
||||
}
|
||||
}
|
||||
|
||||
.poll-footer {
|
||||
border-top-color: rgba(255, 255, 255, 0.2);
|
||||
}
|
||||
|
||||
.poll-voters-list {
|
||||
background: rgba(255, 255, 255, 0.1);
|
||||
}
|
||||
}
|
||||
|
||||
/* ─────────────────────────────────────────────────────────────
|
||||
POLL VOTERS
|
||||
────────────────────────────────────────────────────────────── */
|
||||
.poll-voters {
|
||||
margin-top: 0.5rem;
|
||||
}
|
||||
|
||||
.poll-voters-toggle {
|
||||
cursor: pointer;
|
||||
transition: color 0.2s;
|
||||
|
||||
&:hover {
|
||||
color: var(--bs-primary) !important;
|
||||
text-decoration: underline !important;
|
||||
}
|
||||
|
||||
i {
|
||||
transition: transform 0.2s;
|
||||
font-size: 0.75rem;
|
||||
}
|
||||
}
|
||||
|
||||
.poll-voters-list {
|
||||
animation: slideDown 0.2s ease-out;
|
||||
background: rgba(0, 0, 0, 0.03);
|
||||
padding: 0.5rem 1rem;
|
||||
border-radius: 0.25rem;
|
||||
margin-top: 0.5rem;
|
||||
|
||||
[data-bs-theme="dark"] & {
|
||||
background: rgba(255, 255, 255, 0.05);
|
||||
}
|
||||
}
|
||||
|
||||
.voter-item {
|
||||
padding: 0.25rem 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
i {
|
||||
opacity: 0.6;
|
||||
font-size: 0.875rem;
|
||||
}
|
||||
}
|
||||
|
||||
/* ─────────────────────────────────────────────────────────────
|
||||
ANIMATIONS
|
||||
────────────────────────────────────────────────────────────── */
|
||||
@keyframes slideDown {
|
||||
from {
|
||||
opacity: 0;
|
||||
transform: translateY(-5px);
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,343 @@
|
||||
/**
|
||||
* _message-context-menu.scss
|
||||
* Контекстное меню сообщений (long-press / right-click)
|
||||
*/
|
||||
|
||||
/* ─────────────────────────────────────────────────────────────
|
||||
MESSAGE INTERACTIVE
|
||||
────────────────────────────────────────────────────────────── */
|
||||
[data-message-id] {
|
||||
cursor: pointer;
|
||||
user-select: none;
|
||||
-webkit-user-select: none;
|
||||
-webkit-touch-callout: none;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
/* ─────────────────────────────────────────────────────────────
|
||||
CONTEXT MENU
|
||||
────────────────────────────────────────────────────────────── */
|
||||
.message-context-menu {
|
||||
position: fixed;
|
||||
z-index: 9999;
|
||||
background: #ffffff; // Явно белый для светлой темы
|
||||
border-radius: var(--radius-lg);
|
||||
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.15);
|
||||
min-width: 280px;
|
||||
max-width: 320px;
|
||||
opacity: 0;
|
||||
transform: scale(0.9);
|
||||
transition: opacity 0.2s ease, transform 0.2s ease;
|
||||
overflow: hidden;
|
||||
|
||||
&.show {
|
||||
opacity: 1;
|
||||
transform: scale(1);
|
||||
}
|
||||
|
||||
// Мобильное позиционирование больше не отличается - убрано
|
||||
// (контекстное меню позиционируется одинаково на всех устройствах)
|
||||
|
||||
// Backdrop для мобильных
|
||||
&::before {
|
||||
content: '';
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
background: rgba(0, 0, 0, 0.4);
|
||||
z-index: -1;
|
||||
opacity: 0;
|
||||
transition: opacity 0.2s ease;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
&.show::before {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
// На десктопе backdrop не нужен
|
||||
@media (hover: hover) and (pointer: fine) {
|
||||
&::before {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* ─────────────────────────────────────────────────────────────
|
||||
MESSAGE HIGHLIGHT
|
||||
────────────────────────────────────────────────────────────── */
|
||||
.message-highlighted {
|
||||
background-color: color-mix(in srgb, var(--bs-primary), transparent 92%) !important;
|
||||
transition: background-color 0.2s ease;
|
||||
}
|
||||
|
||||
/* ─────────────────────────────────────────────────────────────
|
||||
QUICK REACTIONS (TELEGRAM-STYLE)
|
||||
────────────────────────────────────────────────────────────── */
|
||||
// Floating панель с реакциями (отдельно от меню)
|
||||
.quick-reactions-panel {
|
||||
position: fixed;
|
||||
z-index: 10000; // Выше чем меню (9999)
|
||||
background: #ffffff; // Явно белый для светлой темы
|
||||
border-radius: 24px;
|
||||
box-shadow: 0 4px 16px rgba(0, 0, 0, 0.12);
|
||||
width: 280px; // Фиксированная ширина как у меню
|
||||
opacity: 0;
|
||||
transform: translateY(10px) scale(0.95);
|
||||
transition: opacity 0.2s ease, transform 0.2s ease, height 0.3s ease;
|
||||
pointer-events: none; // Не блокирует клики когда невидима
|
||||
overflow: hidden; // Для плавной анимации высоты
|
||||
|
||||
&.show {
|
||||
opacity: 1;
|
||||
transform: translateY(0) scale(1);
|
||||
pointer-events: auto;
|
||||
}
|
||||
}
|
||||
|
||||
.quick-reactions-wrapper {
|
||||
position: relative;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 6px 8px;
|
||||
}
|
||||
|
||||
.quick-reactions-list {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 2px;
|
||||
flex-wrap: wrap; // Переносим смайлики на новые строки
|
||||
overflow-x: auto;
|
||||
overflow-y: hidden;
|
||||
max-width: 100%;
|
||||
padding: 4px 0;
|
||||
|
||||
// Скрываем scrollbar
|
||||
scrollbar-width: none;
|
||||
&::-webkit-scrollbar {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
.quick-reaction-btn {
|
||||
flex-shrink: 0;
|
||||
width: 36px;
|
||||
height: 36px;
|
||||
border: none;
|
||||
background: transparent;
|
||||
border-radius: 50%;
|
||||
font-size: 24px;
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
transition: all 0.15s cubic-bezier(0.34, 1.56, 0.64, 1);
|
||||
position: relative;
|
||||
|
||||
&:hover {
|
||||
transform: scale(1.25);
|
||||
background: var(--bs-secondary-bg);
|
||||
}
|
||||
|
||||
&:active {
|
||||
transform: scale(1.1);
|
||||
}
|
||||
|
||||
// Кнопка "показать больше реакций"
|
||||
// TODO: Изменить серый фон на белый для светлой темы (должен совпадать с quick-reaction-btn) и кнопка раскрытия меню со смайликами при белой теме сливается с фоном (не меняется от цвета темы)
|
||||
&.quick-reaction-btn-more {
|
||||
opacity: 0.6;
|
||||
font-size: 20px;
|
||||
|
||||
i {
|
||||
transition: transform 0.3s ease;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
opacity: 1;
|
||||
transform: scale(1.25);
|
||||
|
||||
i {
|
||||
transform: translateY(3px);
|
||||
}
|
||||
}
|
||||
|
||||
&.expanded i {
|
||||
transform: rotate(180deg);
|
||||
}
|
||||
}
|
||||
|
||||
// Анимация появления по очереди
|
||||
opacity: 0;
|
||||
animation: reactionItemAppear 0.2s cubic-bezier(0.34, 1.56, 0.64, 1) forwards;
|
||||
|
||||
@for $i from 1 through 10 {
|
||||
&:nth-child(#{$i}) {
|
||||
animation-delay: #{$i * 0.03}s;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes reactionItemAppear {
|
||||
from {
|
||||
opacity: 0;
|
||||
transform: scale(0.5) translateY(8px);
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: scale(1) translateY(0);
|
||||
}
|
||||
}
|
||||
|
||||
/* ─────────────────────────────────────────────────────────────
|
||||
MENU ACTIONS
|
||||
────────────────────────────────────────────────────────────── */
|
||||
.menu-actions {
|
||||
padding: var(--space-2) 0;
|
||||
}
|
||||
|
||||
.menu-action {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: var(--space-3);
|
||||
padding: var(--space-3) var(--space-4);
|
||||
border: none;
|
||||
background: transparent;
|
||||
cursor: pointer;
|
||||
transition: background-color 0.15s ease;
|
||||
font-size: 15px;
|
||||
color: var(--bs-body-color);
|
||||
text-align: left;
|
||||
|
||||
&:hover {
|
||||
background-color: var(--bs-tertiary-bg);
|
||||
}
|
||||
|
||||
&:active {
|
||||
background-color: var(--bs-secondary-bg);
|
||||
}
|
||||
|
||||
i {
|
||||
font-size: 18px;
|
||||
width: 24px;
|
||||
text-align: center;
|
||||
color: var(--bs-secondary-color);
|
||||
}
|
||||
|
||||
span {
|
||||
flex: 1;
|
||||
font-weight: 500;
|
||||
}
|
||||
}
|
||||
|
||||
.menu-action-danger {
|
||||
color: var(--bs-danger);
|
||||
|
||||
i {
|
||||
color: var(--bs-danger);
|
||||
}
|
||||
|
||||
&:hover {
|
||||
background-color: color-mix(in srgb, var(--bs-danger), transparent 95%);
|
||||
}
|
||||
}
|
||||
|
||||
/* ─────────────────────────────────────────────────────────────
|
||||
TOAST NOTIFICATION
|
||||
────────────────────────────────────────────────────────────── */
|
||||
.context-menu-toast {
|
||||
position: fixed;
|
||||
bottom: 80px;
|
||||
left: 50%;
|
||||
transform: translateX(-50%) translateY(20px);
|
||||
background: rgba(0, 0, 0, 0.85);
|
||||
color: white;
|
||||
padding: var(--space-3) calc(var(--space-3) * 2);
|
||||
border-radius: var(--badge-radius);
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
z-index: 10000;
|
||||
opacity: 0;
|
||||
transition: opacity 0.3s ease, transform 0.3s ease;
|
||||
pointer-events: none;
|
||||
white-space: nowrap;
|
||||
|
||||
&.show {
|
||||
opacity: 1;
|
||||
transform: translateX(-50%) translateY(0);
|
||||
}
|
||||
}
|
||||
|
||||
/* ─────────────────────────────────────────────────────────────
|
||||
DARK THEME
|
||||
────────────────────────────────────────────────────────────── */
|
||||
[data-bs-theme="dark"] {
|
||||
.message-context-menu {
|
||||
background: #2b2d31;
|
||||
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.4);
|
||||
}
|
||||
|
||||
.message-highlighted {
|
||||
background-color: rgba(88, 101, 242, 0.15) !important;
|
||||
}
|
||||
|
||||
.quick-reactions-panel {
|
||||
background: #2b2d31;
|
||||
box-shadow: 0 4px 16px rgba(0, 0, 0, 0.4);
|
||||
}
|
||||
|
||||
.quick-reaction-btn {
|
||||
&:hover {
|
||||
background: rgba(255, 255, 255, 0.1);
|
||||
}
|
||||
}
|
||||
|
||||
.menu-action {
|
||||
color: #dbdee1;
|
||||
|
||||
&:hover {
|
||||
background-color: #35373c;
|
||||
}
|
||||
|
||||
&:active {
|
||||
background-color: #3f4147;
|
||||
}
|
||||
|
||||
i {
|
||||
color: #b5bac1;
|
||||
}
|
||||
}
|
||||
|
||||
.menu-action-danger {
|
||||
color: #f23f43;
|
||||
|
||||
i {
|
||||
color: #f23f43;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
background-color: rgba(242, 63, 67, 0.1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* ─────────────────────────────────────────────────────────────
|
||||
UTILITIES
|
||||
────────────────────────────────────────────────────────────── */
|
||||
.message-context-menu-enabled .reaction-button.add-reaction {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
@keyframes vibrate {
|
||||
0%, 100% { transform: translateX(0); }
|
||||
25% { transform: translateX(-2px); }
|
||||
75% { transform: translateX(2px); }
|
||||
}
|
||||
|
||||
.message-vibrating {
|
||||
animation: vibrate 0.1s linear;
|
||||
}
|
||||
@ -0,0 +1,234 @@
|
||||
/**
|
||||
* message-inputs.scss
|
||||
* Унифицированные стили для полей ввода сообщений и комментариев
|
||||
*/
|
||||
|
||||
$message-bg-default: transparent;
|
||||
$message-bg-focused: transparent;
|
||||
|
||||
.message-field {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: var(--space-1);
|
||||
width: 100%;
|
||||
padding: 0.35rem 0.4rem;
|
||||
border: 1px solid var(--bs-border-color);
|
||||
border-radius: 999px;
|
||||
background: $message-bg-default;
|
||||
transition: border-color 0.15s ease, box-shadow 0.15s ease, background-color 0.15s ease;
|
||||
--message-action-size: 42px;
|
||||
flex-wrap: nowrap; // Важно: запрещаем перенос элементов на новую строку
|
||||
}
|
||||
|
||||
.message-field:focus-within {
|
||||
border-color: var(--bs-primary);
|
||||
box-shadow: 0 0 0 0.2rem rgba(var(--bs-primary-rgb), 0.15);
|
||||
background: $message-bg-focused;
|
||||
}
|
||||
|
||||
.message-field--stacked {
|
||||
align-items: stretch;
|
||||
flex-direction: column;
|
||||
border-radius: 24px;
|
||||
padding: 0.75rem;
|
||||
gap: var(--space-1);
|
||||
}
|
||||
|
||||
.message-field--compact {
|
||||
padding: 0.25rem 0.3rem;
|
||||
border-radius: 24px;
|
||||
--message-action-size: 36px;
|
||||
}
|
||||
|
||||
.message-input,
|
||||
.composer-textarea {
|
||||
flex: 1 1 auto;
|
||||
min-width: 0; // Важно: позволяет сжиматься ниже размера контента
|
||||
border: none !important;
|
||||
background: transparent !important;
|
||||
resize: none;
|
||||
padding: 0.5rem 0.75rem;
|
||||
line-height: 1.4;
|
||||
font-size: 1rem;
|
||||
color: inherit;
|
||||
scrollbar-width: none;
|
||||
box-shadow: none !important;
|
||||
outline: none !important;
|
||||
// Высота управляется через textareaAutoExpand.js для rows="1"
|
||||
// Для остальных textarea используется стандартное поведение
|
||||
}
|
||||
|
||||
.message-field--compact .message-input {
|
||||
font-size: 0.95rem;
|
||||
// min-height удален - управляется textareaAutoExpand.js
|
||||
}
|
||||
|
||||
.message-field--stacked .message-input {
|
||||
min-height: 96px;
|
||||
padding: 0.35rem 0;
|
||||
}
|
||||
|
||||
.message-input:focus {
|
||||
outline: none;
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
.message-input::-webkit-scrollbar {
|
||||
display: none;
|
||||
}
|
||||
|
||||
// Кнопки действий в формах сообщений
|
||||
// Размеры и форма определяются здесь, стили (цвета, эффекты) - в custom-bootstrap.scss
|
||||
.message-icon-btn,
|
||||
.btn-attachment,
|
||||
.btn-send,
|
||||
.message-send,
|
||||
.btn-emoji {
|
||||
width: var(--message-action-size, 42px) !important;
|
||||
min-width: var(--message-action-size, 42px) !important;
|
||||
height: var(--message-action-size, 42px) !important;
|
||||
border-radius: 50% !important;
|
||||
display: inline-flex !important;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 0 !important;
|
||||
aspect-ratio: 1 / 1;
|
||||
flex: 0 0 auto;
|
||||
line-height: 1;
|
||||
font-size: 1.05rem;
|
||||
--bs-btn-padding-x: 0 !important;
|
||||
--bs-btn-padding-y: 0 !important;
|
||||
--bs-btn-border-radius: 50% !important;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
// Кнопка отправки - дополнительная тень
|
||||
.message-send,
|
||||
.btn-send {
|
||||
box-shadow: 0 4px 12px rgba(var(--bs-primary-rgb), 0.2);
|
||||
}
|
||||
|
||||
.message-attach {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.message-attach .dropdown-menu {
|
||||
margin-top: 0.35rem;
|
||||
border-radius: 16px;
|
||||
padding: 0.5rem;
|
||||
}
|
||||
|
||||
.message-emoji .dropdown-menu {
|
||||
min-width: 260px;
|
||||
padding: 0;
|
||||
border-radius: 16px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.chat-emoji-picker {
|
||||
width: min(340px, 90vw);
|
||||
max-width: 360px;
|
||||
height: 340px;
|
||||
border: none;
|
||||
outline: none;
|
||||
|
||||
// Основные переменные для светлой и тёмной темы
|
||||
--background-color: var(--bs-body-bg);
|
||||
--text-color: var(--bs-body-color);
|
||||
--category-font-color: var(--bs-secondary-color);
|
||||
--hover-color: var(--bs-secondary-bg);
|
||||
--button-active-background: var(--bs-primary-bg-subtle, var(--bs-primary));
|
||||
--button-hover-background: var(--bs-secondary-bg);
|
||||
--focus-indicator: var(--bs-primary);
|
||||
--border-color: color-mix(in srgb, var(--bs-border-color), transparent 25%);
|
||||
--input-border-color: color-mix(in srgb, var(--bs-border-color), transparent 30%);
|
||||
--input-placeholder-color: var(--bs-secondary-color);
|
||||
--input-focus-border-color: var(--bs-primary);
|
||||
--indicator-color: var(--bs-primary);
|
||||
|
||||
// RGB переменные для emoji-picker-element (для тёмной темы)
|
||||
--rgb-background: var(--bs-body-bg-rgb);
|
||||
--rgb-color: var(--bs-body-color-rgb);
|
||||
--rgb-accent: var(--bs-primary-rgb);
|
||||
--rgb-input: var(--bs-body-bg-rgb);
|
||||
|
||||
// Скроллбар для светлой темы по умолчанию
|
||||
--scrollbar-color: rgba(0, 0, 0, 0.3);
|
||||
--scrollbar-background: rgba(0, 0, 0, 0.05);
|
||||
|
||||
// Скрываем кнопку выбора тона кожи
|
||||
button.favorites {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
// Кастомный скроллбар для внутренней панели
|
||||
::part(tabpanel) {
|
||||
scrollbar-width: thin;
|
||||
scrollbar-color: var(--scrollbar-color) var(--scrollbar-background);
|
||||
}
|
||||
|
||||
::part(tabpanel)::-webkit-scrollbar {
|
||||
width: 8px;
|
||||
}
|
||||
|
||||
::part(tabpanel)::-webkit-scrollbar-track {
|
||||
background: var(--scrollbar-background);
|
||||
}
|
||||
|
||||
::part(tabpanel)::-webkit-scrollbar-thumb {
|
||||
background: var(--scrollbar-color);
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
::part(tabpanel)::-webkit-scrollbar-thumb:hover {
|
||||
background: rgba(var(--bs-body-color-rgb), 0.4);
|
||||
}
|
||||
}
|
||||
|
||||
// Дополнительные стили для тёмной темы
|
||||
[data-bs-theme="dark"] {
|
||||
.chat-emoji-picker {
|
||||
// Явно устанавливаем цвета для тёмной темы
|
||||
--background-color: var(--bs-body-bg);
|
||||
--text-color: var(--bs-body-color);
|
||||
--secondary-text-color: var(--bs-secondary-color);
|
||||
--category-emoji-size: 1.125rem;
|
||||
|
||||
// Скроллбар для темной темы
|
||||
--scrollbar-color: rgba(255, 255, 255, 0.3);
|
||||
--scrollbar-background: rgba(255, 255, 255, 0.05);
|
||||
}
|
||||
}
|
||||
|
||||
.message-field--solo {
|
||||
display: block;
|
||||
border-radius: 24px;
|
||||
padding: 0.75rem 1rem;
|
||||
}
|
||||
|
||||
.message-field--solo .message-input {
|
||||
width: 100%;
|
||||
min-height: 120px;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
@media (max-width: 576px) {
|
||||
.message-field {
|
||||
// Уменьшаем размеры кнопок на мобильных
|
||||
--message-action-size: 38px;
|
||||
padding: 0.3rem 0.35rem;
|
||||
gap: 0.25rem;
|
||||
}
|
||||
|
||||
.message-field--compact {
|
||||
--message-action-size: 32px;
|
||||
padding: 0.2rem 0.25rem;
|
||||
}
|
||||
|
||||
.message-input,
|
||||
.composer-textarea {
|
||||
// Уменьшаем отступы для экономии места
|
||||
padding: 0.4rem 0.5rem;
|
||||
font-size: 0.95rem;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,335 @@
|
||||
/**
|
||||
* _message-reactions.scss
|
||||
* Реакции на сообщения (эмодзи)
|
||||
*/
|
||||
|
||||
/* ─────────────────────────────────────────────────────────────
|
||||
REACTIONS CONTAINER
|
||||
────────────────────────────────────────────────────────────── */
|
||||
.message-reactions {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 6px; // Увеличен gap для лучшего дыхания
|
||||
margin-top: var(--space-2);
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
/* ─────────────────────────────────────────────────────────────
|
||||
REACTION BUTTON
|
||||
────────────────────────────────────────────────────────────── */
|
||||
.reaction-button {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
padding: 4px 8px;
|
||||
border: 1px solid var(--bs-border-color);
|
||||
border-radius: 12px; // Скругленные края как таблетка
|
||||
background: var(--bs-body-bg); // Контрастный фон (белый/черный в зависимости от темы)
|
||||
cursor: pointer;
|
||||
transition: all 0.2s ease;
|
||||
font-size: 14px;
|
||||
line-height: 1;
|
||||
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.05); // Легкая тень для отделения от фона
|
||||
|
||||
&:hover {
|
||||
background: var(--bs-secondary-bg);
|
||||
border-color: color-mix(in srgb, var(--bs-border-color), var(--bs-body-color) 30%);
|
||||
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
// Активная реакция (пользователь уже отреагировал)
|
||||
&.active {
|
||||
background: #e7f3ff; // Непрозрачный светло-голубой вместо полупрозрачного
|
||||
border-color: var(--bs-primary);
|
||||
color: var(--bs-primary);
|
||||
box-shadow: 0 2px 4px rgba(var(--bs-primary-rgb), 0.2);
|
||||
|
||||
&:hover {
|
||||
background: #d0e9ff; // Чуть темнее при hover
|
||||
}
|
||||
|
||||
.count {
|
||||
color: var(--bs-primary);
|
||||
font-weight: 600; // Жирнее для активных
|
||||
}
|
||||
}
|
||||
|
||||
// Эмодзи внутри кнопки
|
||||
.emoji {
|
||||
font-size: 16px;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
// Счётчик реакций
|
||||
.count {
|
||||
font-size: 12px;
|
||||
font-weight: 500;
|
||||
color: var(--bs-body-color);
|
||||
}
|
||||
}
|
||||
|
||||
/* ─────────────────────────────────────────────────────────────
|
||||
ADD REACTION BUTTON
|
||||
────────────────────────────────────────────────────────────── */
|
||||
.reaction-button.add-reaction {
|
||||
opacity: 0;
|
||||
transition: opacity 0.2s ease;
|
||||
border-style: dashed;
|
||||
|
||||
&:hover {
|
||||
opacity: 1;
|
||||
border-style: solid;
|
||||
}
|
||||
}
|
||||
|
||||
// Показывать кнопку добавления при наведении на сообщение (только на десктопе без контекстного меню)
|
||||
@media (hover: hover) and (pointer: fine) {
|
||||
[data-message-id]:not(.message-context-menu-enabled):hover .reaction-button.add-reaction,
|
||||
.message-reactions:hover .reaction-button.add-reaction {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
// Если есть хотя бы одна реакция, всегда показывать кнопку добавления
|
||||
.message-reactions:has(.reaction-button:not(.add-reaction)) .add-reaction {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
// Полностью скрыть кнопку "+" если включено контекстное меню
|
||||
.message-context-menu-enabled .reaction-button.add-reaction {
|
||||
display: none;
|
||||
}
|
||||
|
||||
/* ─────────────────────────────────────────────────────────────
|
||||
EMOJI PICKER
|
||||
────────────────────────────────────────────────────────────── */
|
||||
.emoji-picker {
|
||||
background: var(--bs-body-bg);
|
||||
border: 1px solid var(--bs-border-color);
|
||||
border-radius: var(--radius-md);
|
||||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
|
||||
padding: var(--space-2);
|
||||
min-width: 200px;
|
||||
}
|
||||
|
||||
.emoji-picker-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(4, 1fr);
|
||||
gap: 4px;
|
||||
}
|
||||
|
||||
.emoji-picker-button {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
border: 1px solid transparent;
|
||||
border-radius: var(--radius-sm);
|
||||
background: transparent;
|
||||
cursor: pointer;
|
||||
font-size: 24px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
transition: all 0.15s ease;
|
||||
|
||||
&:hover {
|
||||
background: var(--bs-tertiary-bg);
|
||||
border-color: var(--bs-border-color);
|
||||
transform: scale(1.1);
|
||||
}
|
||||
|
||||
&:active {
|
||||
transform: scale(0.95);
|
||||
}
|
||||
}
|
||||
|
||||
/* ─────────────────────────────────────────────────────────────
|
||||
ANIMATIONS
|
||||
────────────────────────────────────────────────────────────── */
|
||||
@keyframes reactionPop {
|
||||
0% {
|
||||
transform: scale(0);
|
||||
opacity: 0;
|
||||
}
|
||||
50% {
|
||||
transform: scale(1.2);
|
||||
}
|
||||
100% {
|
||||
transform: scale(1);
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
.reaction-button.new {
|
||||
animation: reactionPop 0.3s ease;
|
||||
}
|
||||
|
||||
/* ─────────────────────────────────────────────────────────────
|
||||
MOBILE RESPONSIVE
|
||||
────────────────────────────────────────────────────────────── */
|
||||
@media (max-width: 576px) {
|
||||
.reaction-button {
|
||||
padding: 3px 6px;
|
||||
font-size: 12px;
|
||||
|
||||
.emoji {
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.count {
|
||||
font-size: 11px;
|
||||
}
|
||||
}
|
||||
|
||||
.emoji-picker-grid {
|
||||
grid-template-columns: repeat(6, 1fr);
|
||||
}
|
||||
|
||||
.emoji-picker-button {
|
||||
width: 36px;
|
||||
height: 36px;
|
||||
font-size: 20px;
|
||||
}
|
||||
}
|
||||
|
||||
/* ─────────────────────────────────────────────────────────────
|
||||
DARK THEME
|
||||
────────────────────────────────────────────────────────────── */
|
||||
[data-bs-theme="dark"] {
|
||||
.reaction-button {
|
||||
background: #2b3035; // Темнее чем bubble для контраста
|
||||
border-color: #495057;
|
||||
color: #f8f9fa;
|
||||
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.3);
|
||||
|
||||
&:hover {
|
||||
background: #343a40;
|
||||
border-color: #6c757d;
|
||||
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.4);
|
||||
}
|
||||
|
||||
&.active {
|
||||
background: #0d3c61;
|
||||
border-color: #0d6efd;
|
||||
color: #4dabf7;
|
||||
box-shadow: 0 2px 4px rgba(13, 110, 253, 0.3);
|
||||
|
||||
&:hover {
|
||||
background: #0a4a7d;
|
||||
}
|
||||
|
||||
.count {
|
||||
color: #4dabf7;
|
||||
font-weight: 600;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.emoji-picker {
|
||||
background: #212529;
|
||||
border-color: #495057;
|
||||
}
|
||||
|
||||
.emoji-picker-button:hover {
|
||||
background: #343a40;
|
||||
border-color: #495057;
|
||||
}
|
||||
|
||||
.reaction-users-popup {
|
||||
background: #2b3035;
|
||||
border-color: #495057;
|
||||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.5);
|
||||
|
||||
.reaction-users-header {
|
||||
border-bottom-color: #495057;
|
||||
}
|
||||
|
||||
.user-name {
|
||||
color: #f8f9fa;
|
||||
|
||||
&:hover {
|
||||
background: #343a40;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* ─────────────────────────────────────────────────────────────
|
||||
REACTION USERS POPUP
|
||||
────────────────────────────────────────────────────────────── */
|
||||
.reaction-users-popup {
|
||||
position: fixed;
|
||||
z-index: 9999;
|
||||
background: var(--bs-body-bg);
|
||||
border: 1px solid var(--bs-border-color);
|
||||
border-radius: 12px;
|
||||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
|
||||
min-width: 180px;
|
||||
max-width: 280px;
|
||||
opacity: 0;
|
||||
transform: translateY(-4px);
|
||||
transition: opacity 0.2s ease, transform 0.2s ease;
|
||||
pointer-events: none;
|
||||
|
||||
&.show {
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
pointer-events: all;
|
||||
}
|
||||
}
|
||||
|
||||
.reaction-users-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
padding: 12px 16px;
|
||||
border-bottom: 1px solid var(--bs-border-color);
|
||||
font-weight: 600;
|
||||
|
||||
.reaction-emoji {
|
||||
font-size: 20px;
|
||||
}
|
||||
|
||||
.reaction-count {
|
||||
color: var(--bs-secondary);
|
||||
font-size: 14px;
|
||||
}
|
||||
}
|
||||
|
||||
.reaction-users-list {
|
||||
max-height: 200px;
|
||||
overflow-y: auto;
|
||||
padding: 4px;
|
||||
|
||||
&::-webkit-scrollbar {
|
||||
width: 6px;
|
||||
}
|
||||
|
||||
&::-webkit-scrollbar-track {
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
&::-webkit-scrollbar-thumb {
|
||||
background: var(--bs-border-color);
|
||||
border-radius: 3px;
|
||||
|
||||
&:hover {
|
||||
background: var(--bs-secondary);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.user-name {
|
||||
padding: 8px 12px;
|
||||
color: var(--bs-body-color);
|
||||
font-size: 14px;
|
||||
border-radius: 6px;
|
||||
transition: background-color 0.15s ease;
|
||||
|
||||
&:hover {
|
||||
background: var(--bs-secondary-bg);
|
||||
}
|
||||
|
||||
&:not(:last-child) {
|
||||
margin-bottom: 2px;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,292 @@
|
||||
/**
|
||||
* _message-selection.scss
|
||||
* Стили для режима выделения сообщений
|
||||
*/
|
||||
|
||||
/* ─────────────────────────────────────────────────────────────
|
||||
SELECTION MODE
|
||||
────────────────────────────────────────────────────────────── */
|
||||
.selection-mode .msg {
|
||||
cursor: pointer;
|
||||
transition: background-color 0.2s;
|
||||
position: relative;
|
||||
padding-left: 40px !important;
|
||||
|
||||
&.justify-content-end {
|
||||
padding-left: 0 !important;
|
||||
padding-right: 40px !important;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
background-color: rgba(0, 123, 255, 0.05);
|
||||
}
|
||||
}
|
||||
|
||||
/* ─────────────────────────────────────────────────────────────
|
||||
CHECKBOX
|
||||
────────────────────────────────────────────────────────────── */
|
||||
.msg-select-checkbox {
|
||||
position: absolute;
|
||||
left: 8px;
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
border: 2px solid var(--bs-border-color);
|
||||
border-radius: 50%;
|
||||
background: var(--bs-body-bg);
|
||||
display: none;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s;
|
||||
z-index: 10;
|
||||
flex-shrink: 0;
|
||||
|
||||
.selection-mode & {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
border-color: var(--bs-primary);
|
||||
background: color-mix(in srgb, var(--bs-primary), transparent 90%);
|
||||
}
|
||||
|
||||
&.selected {
|
||||
background: var(--bs-primary);
|
||||
border-color: var(--bs-primary);
|
||||
|
||||
&::after {
|
||||
content: '✓';
|
||||
color: white;
|
||||
font-size: 14px;
|
||||
font-weight: bold;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.msg.justify-content-end .msg-select-checkbox {
|
||||
left: auto;
|
||||
right: 8px;
|
||||
}
|
||||
|
||||
.msg.selected {
|
||||
background-color: color-mix(in srgb, var(--bs-primary), transparent 90%);
|
||||
}
|
||||
|
||||
/* ─────────────────────────────────────────────────────────────
|
||||
ACTIONS BAR
|
||||
────────────────────────────────────────────────────────────── */
|
||||
.selection-actions-bar {
|
||||
position: fixed;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
background: var(--bs-body-bg);
|
||||
border-top: 2px solid var(--bs-primary);
|
||||
padding: 12px 20px;
|
||||
display: none;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
box-shadow: 0 -2px 10px rgba(0, 0, 0, 0.1);
|
||||
z-index: $z-modal;
|
||||
animation: slideUp 0.3s ease-out;
|
||||
|
||||
&.active {
|
||||
display: flex;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes slideUp {
|
||||
from {
|
||||
transform: translateY(100%);
|
||||
}
|
||||
to {
|
||||
transform: translateY(0);
|
||||
}
|
||||
}
|
||||
|
||||
.selection-count {
|
||||
font-weight: 600;
|
||||
color: var(--bs-primary);
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
.selection-actions {
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
|
||||
.btn {
|
||||
padding: 8px 20px;
|
||||
font-weight: 500;
|
||||
border-radius: var(--button-radius);
|
||||
transition: all 0.2s;
|
||||
|
||||
&:hover {
|
||||
transform: translateY(-1px);
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* ─────────────────────────────────────────────────────────────
|
||||
FORWARD MODAL
|
||||
────────────────────────────────────────────────────────────── */
|
||||
.chat-forward-modal {
|
||||
.modal-dialog {
|
||||
max-width: 500px;
|
||||
}
|
||||
}
|
||||
|
||||
.chat-list-item {
|
||||
padding: 12px 16px;
|
||||
border-bottom: 1px solid var(--bs-border-color);
|
||||
cursor: pointer;
|
||||
transition: background-color 0.2s;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
|
||||
&:hover {
|
||||
background-color: var(--bs-tertiary-bg);
|
||||
}
|
||||
|
||||
&:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
}
|
||||
|
||||
.chat-list-avatar {
|
||||
width: 48px;
|
||||
height: 48px;
|
||||
border-radius: 50%;
|
||||
background: var(--bs-tertiary-bg);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: 20px;
|
||||
color: var(--bs-secondary-color);
|
||||
flex-shrink: 0;
|
||||
overflow: hidden;
|
||||
position: relative;
|
||||
|
||||
img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: cover;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.chat-list-info {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.chat-list-name {
|
||||
font-weight: 600;
|
||||
color: var(--bs-body-color);
|
||||
margin-bottom: 2px;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.chat-list-subtitle {
|
||||
font-size: 13px;
|
||||
color: var(--bs-secondary-color);
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.chat-list-loading,
|
||||
.chat-list-empty {
|
||||
padding: 40px 20px;
|
||||
text-align: center;
|
||||
color: var(--bs-secondary-color);
|
||||
}
|
||||
|
||||
/* ─────────────────────────────────────────────────────────────
|
||||
SEARCH
|
||||
────────────────────────────────────────────────────────────── */
|
||||
.chat-forward-search {
|
||||
padding: 12px 16px;
|
||||
border-bottom: 1px solid var(--bs-border-color);
|
||||
|
||||
input {
|
||||
width: 100%;
|
||||
padding: 8px 12px;
|
||||
border: 1px solid var(--bs-border-color);
|
||||
border-radius: var(--radius-md);
|
||||
font-size: 14px;
|
||||
background: var(--bs-body-bg);
|
||||
color: var(--bs-body-color);
|
||||
|
||||
&:focus {
|
||||
outline: none;
|
||||
border-color: var(--bs-primary);
|
||||
box-shadow: 0 0 0 3px color-mix(in srgb, var(--bs-primary), transparent 90%);
|
||||
}
|
||||
|
||||
&::placeholder {
|
||||
color: var(--bs-secondary-color);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* ─────────────────────────────────────────────────────────────
|
||||
FORWARDED INDICATOR
|
||||
────────────────────────────────────────────────────────────── */
|
||||
.forwarded-indicator {
|
||||
font-size: 12px;
|
||||
color: var(--bs-secondary-color);
|
||||
margin-bottom: 4px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
font-style: italic;
|
||||
|
||||
i {
|
||||
font-size: 14px;
|
||||
}
|
||||
}
|
||||
|
||||
/* ─────────────────────────────────────────────────────────────
|
||||
MOBILE RESPONSIVE
|
||||
────────────────────────────────────────────────────────────── */
|
||||
@media (max-width: 768px) {
|
||||
.selection-actions-bar {
|
||||
padding: 10px 15px;
|
||||
}
|
||||
|
||||
.selection-count {
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.selection-actions .btn {
|
||||
padding: 6px 16px;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.msg-select-checkbox {
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
left: 5px;
|
||||
}
|
||||
|
||||
.msg.justify-content-end .msg-select-checkbox {
|
||||
right: 5px;
|
||||
}
|
||||
|
||||
.selection-mode .msg {
|
||||
padding-left: 32px !important;
|
||||
|
||||
&.justify-content-end {
|
||||
padding-left: 0 !important;
|
||||
padding-right: 32px !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
201
backend/static/scss_backup_20260115/custom-bootstrap.scss
Normal file
201
backend/static/scss_backup_20260115/custom-bootstrap.scss
Normal file
@ -0,0 +1,201 @@
|
||||
// =============================================================================
|
||||
// Custom Bootstrap Configuration
|
||||
// =============================================================================
|
||||
// Этот файл переопределяет переменные Bootstrap ДО его компиляции
|
||||
// Результат: полностью кастомизированный Bootstrap с нашей системой отступов
|
||||
|
||||
// =============================================================================
|
||||
// 1. ИМПОРТ НАШИХ ПЕРЕМЕННЫХ
|
||||
// =============================================================================
|
||||
// Сначала импортируем наши CSS переменные (для справки)
|
||||
// Реальные значения ниже в SCSS переменных
|
||||
|
||||
// =============================================================================
|
||||
// 2. ПЕРЕОПРЕДЕЛЕНИЕ BOOTSTRAP ПЕРЕМЕННЫХ
|
||||
// =============================================================================
|
||||
|
||||
// ──────────────────────────────────────────────────────────────
|
||||
// 2.1 Система отступов (8px grid)
|
||||
// ──────────────────────────────────────────────────────────────
|
||||
// Bootstrap использует $spacer для всех utility классов (.p-*, .m-* и т.д.)
|
||||
// Переопределяем его на нашу 8px сетку
|
||||
|
||||
$spacer: 1rem !default; // 16px - базовое значение
|
||||
|
||||
// Переопределение всей шкалы spacing
|
||||
$spacers: (
|
||||
0: 0, // 0px
|
||||
1: 0.25rem, // 4px - var(--space-1)
|
||||
2: 0.5rem, // 8px - var(--space-2)
|
||||
3: 0.75rem, // 12px - var(--space-3)
|
||||
4: 1rem, // 16px - var(--space-4)
|
||||
5: 1.5rem, // 24px - var(--space-5)
|
||||
) !default;
|
||||
|
||||
// ──────────────────────────────────────────────────────────────
|
||||
// 2.2 Grid система
|
||||
// ──────────────────────────────────────────────────────────────
|
||||
$grid-gutter-width: 0.5rem !default; // 8px - var(--space-2)
|
||||
|
||||
// ──────────────────────────────────────────────────────────────
|
||||
// 2.3 Компоненты - Padding
|
||||
// ──────────────────────────────────────────────────────────────
|
||||
// Переопределяем padding для всех Bootstrap компонентов
|
||||
|
||||
// Cards
|
||||
$card-spacer-y: 1rem !default; // var(--space-2)
|
||||
$card-spacer-x: 1rem !default; // var(--space-2)
|
||||
|
||||
// Alerts
|
||||
$alert-padding-y: 1rem !default; // var(--space-2)
|
||||
$alert-padding-x: 1rem !default; // var(--space-2)
|
||||
|
||||
// Buttons
|
||||
$btn-padding-y: 0.5rem !default; // var(--space-1)
|
||||
$btn-padding-x: 1rem !default; // var(--space-2)
|
||||
|
||||
$btn-padding-y-sm: 0.5rem !default; // var(--space-1)
|
||||
$btn-padding-x-sm: 0.5rem !default; // var(--space-1)
|
||||
|
||||
$btn-padding-y-lg: 1rem !default; // var(--space-2)
|
||||
$btn-padding-x-lg: 1.5rem !default; // var(--space-3)
|
||||
|
||||
// Form controls
|
||||
$input-padding-y: 0.5rem !default; // var(--space-1)
|
||||
$input-padding-x: 1rem !default; // var(--space-2)
|
||||
|
||||
$input-padding-y-sm: 0.5rem !default; // var(--space-1)
|
||||
$input-padding-x-sm: 0.5rem !default; // var(--space-1)
|
||||
|
||||
$input-padding-y-lg: 1rem !default; // var(--space-2)
|
||||
$input-padding-x-lg: 1.5rem !default; // var(--space-3)
|
||||
|
||||
// Modals
|
||||
$modal-inner-padding: 1rem !default; // var(--space-2)
|
||||
$modal-header-padding-y: 1rem !default; // var(--space-2)
|
||||
$modal-header-padding-x: 1rem !default; // var(--space-2)
|
||||
|
||||
// Dropdowns
|
||||
$dropdown-padding-x: 0 !default;
|
||||
$dropdown-padding-y: 8px !default; // var(--space-1)
|
||||
$dropdown-item-padding-y: 8px !default; // var(--space-1)
|
||||
$dropdown-item-padding-x: 16px !default; // var(--space-2)
|
||||
|
||||
// Navs
|
||||
$nav-link-padding-y: 8px !default; // var(--space-1)
|
||||
$nav-link-padding-x: 16px !default; // var(--space-2)
|
||||
|
||||
// Navbar
|
||||
$navbar-padding-y: 8px !default; // var(--space-1)
|
||||
$navbar-padding-x: 16px !default; // var(--space-2)
|
||||
|
||||
// Pagination
|
||||
$pagination-padding-y: 8px !default; // var(--space-1)
|
||||
$pagination-padding-x: 16px !default; // var(--space-2)
|
||||
|
||||
$pagination-padding-y-sm: 8px !default; // var(--space-1)
|
||||
$pagination-padding-x-sm: 8px !default; // var(--space-1)
|
||||
|
||||
$pagination-padding-y-lg: 16px !default; // var(--space-2)
|
||||
$pagination-padding-x-lg: 24px !default; // var(--space-3)
|
||||
|
||||
// List groups
|
||||
$list-group-item-padding-y: 1rem !default; // var(--space-2)
|
||||
$list-group-item-padding-x: 1rem !default; // var(--space-2)
|
||||
|
||||
// Breadcrumbs
|
||||
$breadcrumb-padding-y: 0.5rem !default; // var(--space-1)
|
||||
$breadcrumb-padding-x: 1rem !default; // var(--space-2)
|
||||
|
||||
// Toasts
|
||||
$toast-padding-x: 1rem !default; // var(--space-2)
|
||||
$toast-padding-y: 0.5rem !default; // var(--space-1)
|
||||
|
||||
// Offcanvas
|
||||
$offcanvas-padding-y: 1rem !default; // var(--space-2)
|
||||
$offcanvas-padding-x: 1rem !default; // var(--space-2)
|
||||
|
||||
// Badges
|
||||
$badge-padding-y: 0.5rem !default; // var(--space-1)
|
||||
$badge-padding-x: 0.5rem !default; // var(--space-1)
|
||||
|
||||
// ──────────────────────────────────────────────────────────────
|
||||
// 2.4 Cards - переопределяем стили карточек (GLASSMORPHISM)
|
||||
// ──────────────────────────────────────────────────────────────
|
||||
$card-border-radius: 18px !default;
|
||||
$card-border-width: 1px !default;
|
||||
$card-border-color: rgba(255, 255, 255, 0.1) !default; // Светлая граница для glassmorphism
|
||||
$card-bg: rgba(255, 255, 255, 0.05) !default; // Полупрозрачный для эффекта стекла
|
||||
$card-box-shadow: 0 8px 32px 0 rgba(0, 0, 0, 0.37) !default; // Глубокая тень
|
||||
|
||||
// Card header/footer
|
||||
$card-cap-padding-y: 1rem !default;
|
||||
$card-cap-padding-x: 1rem !default;
|
||||
$card-cap-bg: transparent !default; // Прозрачный header/footer
|
||||
$card-cap-color: null !default;
|
||||
|
||||
// ──────────────────────────────────────────────────────────────
|
||||
// 2.5 Дополнительные настройки (опционально)
|
||||
// ──────────────────────────────────────────────────────────────
|
||||
|
||||
// Border radius (если хотите унифицировать)
|
||||
// $border-radius: 10px !default; // var(--radius-md)
|
||||
// $border-radius-sm: 6px !default; // var(--radius-sm)
|
||||
// $border-radius-lg: 14px !default; // var(--radius-lg)
|
||||
|
||||
// =============================================================================
|
||||
// 3. ИМПОРТ ABSTRACTS (Design Tokens & Mixins)
|
||||
// =============================================================================
|
||||
// Импортируем переменные и миксины ДО Bootstrap
|
||||
@import "abstracts/variables";
|
||||
@import "abstracts/mixins";
|
||||
|
||||
// =============================================================================
|
||||
// 4. ИМПОРТ BOOTSTRAP
|
||||
// =============================================================================
|
||||
// Импортируем Bootstrap ПОСЛЕ переопределения переменных
|
||||
@import "../node_modules/bootstrap/scss/bootstrap";
|
||||
|
||||
// =============================================================================
|
||||
// 4.5 CSS ПЕРЕМЕННЫЕ (RUNTIME)
|
||||
// =============================================================================
|
||||
// CSS custom properties для использования в браузере
|
||||
@import "abstracts/css-variables";
|
||||
|
||||
// =============================================================================
|
||||
// 5. КАСТОМНЫЕ КОМПОНЕНТЫ
|
||||
// =============================================================================
|
||||
|
||||
// Layout
|
||||
@import "layout/chat-layout";
|
||||
|
||||
// Базовые компоненты
|
||||
@import "components/common";
|
||||
@import "components/navbar";
|
||||
@import "components/sidebar";
|
||||
@import "components/page-header";
|
||||
@import "components/cards";
|
||||
|
||||
// Badge компоненты
|
||||
@import "components/badges-bem";
|
||||
|
||||
// Чат компоненты (в подпапке chat/)
|
||||
@import "components/chat/message-inputs";
|
||||
@import "components/chat/chat-detail";
|
||||
@import "components/chat/chat-date-groups";
|
||||
@import "components/chat/chat-list-enhanced";
|
||||
@import "components/chat/chat-enhanced";
|
||||
@import "components/chat/chat-polls";
|
||||
@import "components/chat/chat-new-messages-indicator";
|
||||
@import "components/chat/message-selection";
|
||||
@import "components/chat/message-context-menu";
|
||||
@import "components/chat/message-reactions";
|
||||
|
||||
// Дополнительные компоненты
|
||||
@import "components/comments";
|
||||
@import "components/modals";
|
||||
|
||||
// Остальные компоненты (загружаются напрямую из CSS - не мигрировано)
|
||||
// TODO: Мигрировать buttons, card-list, document-list, request-list,
|
||||
// ios-search, feed-specific, base-app, request-detail
|
||||
|
||||
397
backend/static/scss_backup_20260115/layout/_base-app.scss
Normal file
397
backend/static/scss_backup_20260115/layout/_base-app.scss
Normal file
@ -0,0 +1,397 @@
|
||||
// =============================================================================
|
||||
// Base App Layout & Styles (SCSS)
|
||||
// =============================================================================
|
||||
// Базовые стили приложения: layout, navbar, sidebar, формы, feed-элементы
|
||||
//
|
||||
// Зависимости:
|
||||
// - Bootstrap custom build
|
||||
// - CSS переменные из abstracts/_css-variables.scss
|
||||
|
||||
// =============================================================================
|
||||
// ПЕРЕМЕННЫЕ
|
||||
// =============================================================================
|
||||
|
||||
// Цвета
|
||||
$primary-light: #007AFF;
|
||||
$primary-dark: #0A84FF;
|
||||
$link-hover-light: #0062CC;
|
||||
$link-hover-dark: #59AFFF;
|
||||
|
||||
// Фоны темной темы
|
||||
$dark-bg: #1C1C1E;
|
||||
$dark-color: #F2F2F7;
|
||||
$dark-border: #3A3A3C;
|
||||
$dark-tertiary-bg: #2C2C2E;
|
||||
|
||||
// Типографика
|
||||
$font-family-base: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
|
||||
$font-size-base: 17px;
|
||||
|
||||
// Размеры компонентов
|
||||
$btn-border-radius: 10px;
|
||||
$btn-min-height: 44px;
|
||||
$form-control-border-radius: 10px;
|
||||
$form-control-height: 44px;
|
||||
$dropdown-border-radius: 13px;
|
||||
|
||||
// Переходы
|
||||
$btn-transition: background-color 0.3s, transform 0.1s;
|
||||
$form-transition: box-shadow 0.2s ease, border-color 0.2s ease;
|
||||
$ghost-btn-transition: background 0.2s ease;
|
||||
|
||||
// Тени
|
||||
$dropdown-shadow: 0 6px 24px rgba(0, 0, 0, 0.12);
|
||||
|
||||
// Navbar
|
||||
$navbar-border-width: 0.5px;
|
||||
|
||||
// =============================================================================
|
||||
// ЦВЕТОВАЯ СХЕМА
|
||||
// =============================================================================
|
||||
|
||||
:root {
|
||||
--bs-primary: #{$primary-light};
|
||||
--bs-link-color: var(--bs-primary);
|
||||
--bs-link-hover-color: #{$link-hover-light};
|
||||
}
|
||||
|
||||
[data-bs-theme="dark"] {
|
||||
--bs-body-bg: #{$dark-bg};
|
||||
--bs-body-color: #{$dark-color};
|
||||
--bs-border-color: #{$dark-border};
|
||||
--bs-tertiary-bg: #{$dark-tertiary-bg};
|
||||
--bs-primary: #{$primary-dark};
|
||||
--bs-link-color: var(--bs-primary);
|
||||
--bs-link-hover-color: #{$link-hover-dark};
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// BODY & БАЗОВЫЕ СТИЛИ
|
||||
// =============================================================================
|
||||
|
||||
body {
|
||||
font-family: $font-family-base;
|
||||
font-size: $font-size-base;
|
||||
padding-top: var(--navbar-h);
|
||||
overflow-x: hidden;
|
||||
background-color: var(--bs-body-bg);
|
||||
|
||||
// Safe area для iOS
|
||||
@supports(padding-top: env(safe-area-inset-top)) {
|
||||
padding-top: calc(var(--navbar-h) + env(safe-area-inset-top));
|
||||
}
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// ФОРМЫ И КНОПКИ (БАЗОВЫЕ ПЕРЕОПРЕДЕЛЕНИЯ)
|
||||
// =============================================================================
|
||||
|
||||
.btn {
|
||||
border-radius: $btn-border-radius;
|
||||
min-height: $btn-min-height;
|
||||
transition: $btn-transition;
|
||||
|
||||
&:hover {
|
||||
transform: scale(1.02);
|
||||
}
|
||||
}
|
||||
|
||||
.form-control {
|
||||
border-radius: $form-control-border-radius;
|
||||
height: $form-control-height;
|
||||
font-size: $font-size-base;
|
||||
|
||||
&:focus {
|
||||
box-shadow: 0 0 0 2px rgba(var(--bs-primary-rgb), 0.25);
|
||||
border-color: var(--bs-primary);
|
||||
}
|
||||
}
|
||||
|
||||
.dropdown-menu {
|
||||
border-radius: $dropdown-border-radius;
|
||||
box-shadow: $dropdown-shadow;
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// NAVBAR
|
||||
// =============================================================================
|
||||
|
||||
.app-navbar {
|
||||
position: fixed;
|
||||
inset: 0 0 auto 0;
|
||||
z-index: 1030;
|
||||
border-bottom: $navbar-border-width solid var(--bs-border-color);
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// LAYOUT GRID SYSTEM
|
||||
// =============================================================================
|
||||
|
||||
.app-layout {
|
||||
display: block;
|
||||
}
|
||||
|
||||
// Адаптивный контейнер
|
||||
.content-container {
|
||||
width: 100%;
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
}
|
||||
|
||||
// Мобильные устройства
|
||||
@media (max-width: 991.98px) {
|
||||
.content-container {
|
||||
padding-left: var(--bs-gutter-x, 0.75rem);
|
||||
padding-right: var(--bs-gutter-x, 0.75rem);
|
||||
}
|
||||
}
|
||||
|
||||
// Десктоп с grid layout
|
||||
@media (min-width: 992px) {
|
||||
// При наличии сайдбаров убираем padding и max-width
|
||||
.app-layout.with-sidebar,
|
||||
.app-layout.with-rightbar {
|
||||
.content-container {
|
||||
max-width: none;
|
||||
padding-left: 0;
|
||||
padding-right: 0;
|
||||
}
|
||||
}
|
||||
|
||||
// Grid: sidebar + content + rightbar
|
||||
.app-layout.with-sidebar.with-rightbar {
|
||||
display: grid;
|
||||
grid-template-columns: var(--sidebar-w) 1fr var(--rightbar-w);
|
||||
gap: var(--layout-gap);
|
||||
align-items: start;
|
||||
}
|
||||
|
||||
// Grid: sidebar + content
|
||||
.app-layout.with-sidebar:not(.with-rightbar) {
|
||||
display: grid;
|
||||
grid-template-columns: var(--sidebar-w) 1fr;
|
||||
gap: var(--layout-gap);
|
||||
align-items: start;
|
||||
}
|
||||
|
||||
// Без сайдбаров - стандартный container
|
||||
.app-layout:not(.with-sidebar):not(.with-rightbar) {
|
||||
.content-container {
|
||||
padding-left: var(--bs-gutter-x, 0.75rem);
|
||||
padding-right: var(--bs-gutter-x, 0.75rem);
|
||||
max-width: 960px;
|
||||
}
|
||||
}
|
||||
|
||||
// Компенсация для .row внутри grid-layout
|
||||
.app-layout.with-sidebar,
|
||||
.app-layout.with-rightbar {
|
||||
.content-container > .row {
|
||||
margin-left: 0;
|
||||
margin-right: 0;
|
||||
|
||||
> [class*="col-"] {
|
||||
padding-left: calc(var(--bs-gutter-x) * 0.5);
|
||||
padding-right: calc(var(--bs-gutter-x) * 0.5);
|
||||
}
|
||||
}
|
||||
|
||||
// Карточки внутри колонок
|
||||
.container > .row > [class*="col-"] {
|
||||
> .card,
|
||||
> section.card {
|
||||
max-width: 100%;
|
||||
min-width: 0;
|
||||
overflow: hidden;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Breakpoints для max-width
|
||||
@media (min-width: 1200px) {
|
||||
.app-layout:not(.with-sidebar):not(.with-rightbar) .content-container {
|
||||
max-width: 1140px;
|
||||
}
|
||||
}
|
||||
|
||||
@media (min-width: 1400px) {
|
||||
.app-layout:not(.with-sidebar):not(.with-rightbar) .content-container {
|
||||
max-width: 1320px;
|
||||
}
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// SIDEBAR
|
||||
// =============================================================================
|
||||
|
||||
#sidebar {
|
||||
--bs-offcanvas-width: var(--sidebar-w);
|
||||
border-radius: 0 13px 13px 0;
|
||||
|
||||
@media (min-width: 992px) {
|
||||
&.offcanvas-lg {
|
||||
position: sticky;
|
||||
top: var(--navbar-h);
|
||||
height: calc(100dvh - var(--navbar-h));
|
||||
align-self: start;
|
||||
overflow: auto;
|
||||
border-right: $navbar-border-width solid var(--bs-border-color);
|
||||
background: var(--bs-body-bg);
|
||||
transform: none !important;
|
||||
visibility: visible !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// NOTIFICATION BELL
|
||||
// =============================================================================
|
||||
|
||||
.bell-wrapper {
|
||||
position: relative;
|
||||
line-height: 1;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.bell-dot {
|
||||
position: absolute;
|
||||
top: -2px;
|
||||
right: -2px;
|
||||
width: 9px;
|
||||
height: 9px;
|
||||
border-radius: 999px;
|
||||
background: var(--bs-danger);
|
||||
color: #fff;
|
||||
font-size: 7px;
|
||||
line-height: 9px;
|
||||
font-weight: 700;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
box-shadow: 0 0 0 1px rgba(255, 255, 255, 0.65);
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// BUTTONS & GHOST BUTTONS
|
||||
// =============================================================================
|
||||
|
||||
.btn-ghost {
|
||||
background: color-mix(in srgb, var(--bs-body-bg) 94%, transparent);
|
||||
color: var(--bs-body-color);
|
||||
padding: var(--space-1);
|
||||
border-radius: 999px;
|
||||
font-size: 0.9rem;
|
||||
transition: $ghost-btn-transition;
|
||||
|
||||
&:hover {
|
||||
background: color-mix(in srgb, var(--bs-body-bg) 88%, transparent);
|
||||
}
|
||||
|
||||
.bi {
|
||||
font-size: 1rem;
|
||||
margin-right: 0.35rem;
|
||||
vertical-align: -2px;
|
||||
}
|
||||
|
||||
// Текст кнопок с toggle (Скрыть/Показать)
|
||||
.txt-close {
|
||||
display: none;
|
||||
}
|
||||
|
||||
&[aria-expanded="true"] {
|
||||
.txt-open {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.txt-close {
|
||||
display: inline;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// FEED ELEMENTS
|
||||
// =============================================================================
|
||||
|
||||
// Блоки типа "Новые сотрудники"
|
||||
.feed-new {
|
||||
border-radius: var(--feed-radius);
|
||||
border: 1px solid color-mix(in srgb, var(--bs-border-color), transparent 10%);
|
||||
background: var(--bs-body-bg);
|
||||
box-shadow: var(--feed-shadow);
|
||||
padding: var(--card-padding);
|
||||
}
|
||||
|
||||
// Z-index для stretched-link
|
||||
.card-body .stretched-link::after,
|
||||
.feed-img .stretched-link::after {
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.collapse {
|
||||
position: relative;
|
||||
z-index: 2;
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// ALPHA HEADERS (буквенные разделители)
|
||||
// =============================================================================
|
||||
|
||||
.alpha-header {
|
||||
position: sticky;
|
||||
top: calc(var(--navbar-h) + var(--space-1));
|
||||
z-index: 1;
|
||||
background: color-mix(in srgb, var(--bs-body-bg) 92%, transparent);
|
||||
backdrop-filter: blur(6px);
|
||||
border: 1px solid color-mix(in srgb, var(--bs-border-color), transparent 30%);
|
||||
color: var(--bs-secondary-color);
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: var(--space-1);
|
||||
padding: var(--space-1);
|
||||
border-radius: 999px;
|
||||
font-weight: 700;
|
||||
font-size: 0.85rem;
|
||||
margin: var(--space-1) 0 var(--space-1) var(--space-1);
|
||||
}
|
||||
|
||||
.alpha-dot {
|
||||
width: 6px;
|
||||
height: 6px;
|
||||
border-radius: 999px;
|
||||
background: var(--bs-primary);
|
||||
opacity: 0.6;
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// ACTION BUTTONS
|
||||
// =============================================================================
|
||||
|
||||
.action-btn {
|
||||
min-height: unset;
|
||||
padding: var(--space-1);
|
||||
|
||||
.bi {
|
||||
font-size: 1rem;
|
||||
}
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// TEXT UTILITIES
|
||||
// =============================================================================
|
||||
|
||||
// Переносим длинные слова/ссылки
|
||||
.feed-text,
|
||||
.feed-full {
|
||||
overflow-wrap: anywhere;
|
||||
width: 100%;
|
||||
margin-right: 0;
|
||||
padding-right: 0;
|
||||
}
|
||||
|
||||
// Z-index для кнопки "Подробнее"
|
||||
.readmore-toggle {
|
||||
position: relative;
|
||||
z-index: 2;
|
||||
}
|
||||
61
backend/static/scss_backup_20260115/layout/_chat-layout.scss
Normal file
61
backend/static/scss_backup_20260115/layout/_chat-layout.scss
Normal file
@ -0,0 +1,61 @@
|
||||
// =============================================================================
|
||||
// Chat Layout
|
||||
// =============================================================================
|
||||
// Глобальная структура страниц чата (не специфичные компоненты)
|
||||
|
||||
// =============================================================================
|
||||
// 1. MAIN CONTAINER
|
||||
// =============================================================================
|
||||
|
||||
main.main {
|
||||
padding-bottom: 0 !important;
|
||||
}
|
||||
|
||||
// Специальное правило для страниц чата в grid layout
|
||||
// Ограничиваем высоту main, чтобы не ломать grid и rightbar
|
||||
@media (min-width: 992px) {
|
||||
.app-layout.with-rightbar main.main:has(.chat-page),
|
||||
.app-layout.with-sidebar main.main:has(.chat-page) {
|
||||
max-height: calc(100vh - var(--navbar-h, 60px) - 2 * var(--layout-padding-y, 12px));
|
||||
overflow: hidden;
|
||||
}
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// 2. CHAT PAGE STRUCTURE
|
||||
// =============================================================================
|
||||
|
||||
// Root контейнер страницы чата
|
||||
@supports (height: 100svh) {
|
||||
.chat-root {
|
||||
height: calc(100svh - var(--navbar-h));
|
||||
}
|
||||
}
|
||||
|
||||
.chat-root > .row {
|
||||
height: 100%;
|
||||
min-height: 0;
|
||||
}
|
||||
|
||||
// Column layout
|
||||
.chat-col {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 93%;
|
||||
min-height: 0;
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// 3. CHAT BOX CONTAINER
|
||||
// =============================================================================
|
||||
|
||||
.card.chat-box {
|
||||
position: relative;
|
||||
flex: 1 1 auto;
|
||||
min-height: 0;
|
||||
display: grid;
|
||||
grid-template-rows: 1fr auto; /* messages + composer */
|
||||
overflow: hidden;
|
||||
margin-bottom: 0;
|
||||
background: var(--bs-body-bg);
|
||||
}
|
||||
85
backend/static/scss_backup_20260115/main.scss
Normal file
85
backend/static/scss_backup_20260115/main.scss
Normal file
@ -0,0 +1,85 @@
|
||||
// =============================================================================
|
||||
// Main Application SCSS
|
||||
// =============================================================================
|
||||
// Главный файл для сборки всех стилей приложения (кроме Bootstrap)
|
||||
// Компилируется в css/app.css
|
||||
|
||||
// =============================================================================
|
||||
// 1. ABSTRACTS - Переменные, миксины, функции
|
||||
// =============================================================================
|
||||
@import 'abstracts/index';
|
||||
|
||||
// =============================================================================
|
||||
// 2. LAYOUT - Структура приложения
|
||||
// =============================================================================
|
||||
@import 'layout/base-app';
|
||||
@import 'layout/chat-layout';
|
||||
|
||||
// =============================================================================
|
||||
// 3. COMPONENTS - Переиспользуемые компоненты
|
||||
// =============================================================================
|
||||
|
||||
// Базовые компоненты
|
||||
@import 'components/buttons';
|
||||
@import 'components/cards';
|
||||
@import 'components/modals';
|
||||
@import 'components/navbar';
|
||||
@import 'components/sidebar';
|
||||
@import 'components/page-header';
|
||||
@import 'components/common';
|
||||
|
||||
// Spacing и layout utilities
|
||||
@import 'components/layout-spacing';
|
||||
@import 'components/spacing-utils';
|
||||
|
||||
// iOS-стиль компоненты
|
||||
@import 'components/ios-search';
|
||||
@import 'components/ios-components';
|
||||
|
||||
// Бейджи
|
||||
@import 'components/badges-bem';
|
||||
@import 'components/badges-telegram-style';
|
||||
|
||||
// Комментарии
|
||||
@import 'components/comments';
|
||||
|
||||
// Списки и уведомления
|
||||
@import 'components/card-list';
|
||||
@import 'components/notifications';
|
||||
|
||||
// Заявления и документы
|
||||
@import 'components/request-list';
|
||||
@import 'components/request-detail';
|
||||
@import 'components/document-list';
|
||||
@import 'components/recipientPicker';
|
||||
@import 'components/list-view';
|
||||
|
||||
// Feed компоненты
|
||||
@import 'components/feed-specific';
|
||||
@import 'components/join-scroller';
|
||||
@import 'components/post-form';
|
||||
|
||||
// Модальные окна
|
||||
@import 'components/modal-overrides';
|
||||
@import 'components/logout-modal';
|
||||
|
||||
// Employees компоненты
|
||||
@import 'components/department-controls';
|
||||
@import 'components/team-wheel';
|
||||
|
||||
// Календарь и rightbar
|
||||
@import 'components/rightbar-calendar';
|
||||
|
||||
// =============================================================================
|
||||
// 4. UTILITIES - Вспомогательные классы
|
||||
// =============================================================================
|
||||
// @import 'utilities/spacing-utils';
|
||||
// @import 'utilities/index';
|
||||
|
||||
// =============================================================================
|
||||
// ПРИМЕЧАНИЯ
|
||||
// =============================================================================
|
||||
// - Bootstrap подключается отдельно через bootstrap-custom.css
|
||||
// - CSS переменные (--variable) определены в abstracts/_css-variables.scss
|
||||
// - SCSS переменные ($variable) определены в abstracts/_variables.scss
|
||||
// - Порядок импортов критичен для CSS каскада
|
||||
461
docs/reports/SCSS_ARCHITECTURE_AUDIT.md
Normal file
461
docs/reports/SCSS_ARCHITECTURE_AUDIT.md
Normal file
@ -0,0 +1,461 @@
|
||||
# Аудит архитектуры backend/static/scss
|
||||
|
||||
**Дата:** 15 января 2026
|
||||
**Аудитор:** GitHub Copilot
|
||||
**Статус:** ⚠️ Требуется улучшение
|
||||
|
||||
## 📊 Общая оценка: 7/10
|
||||
|
||||
### ✅ Сильные стороны (7 баллов)
|
||||
|
||||
1. **Хорошая структура папок** (9/10)
|
||||
- ✅ Логичное разделение: abstracts, layout, components, utilities
|
||||
- ✅ Подпапка chat для chat компонентов
|
||||
- ✅ Понятная организация
|
||||
|
||||
2. **Именование файлов** (8/10)
|
||||
- ✅ Префикс `_` для partials
|
||||
- ✅ Kebab-case naming
|
||||
- ✅ Понятные имена
|
||||
|
||||
3. **Документация** (7/10)
|
||||
- ✅ Есть BUTTONS_README.md
|
||||
- ✅ Комментарии в main.scss
|
||||
- ⚠️ Нет общего README в scss/
|
||||
|
||||
4. **Модульность** (9/10)
|
||||
- ✅ 30 компонентов в components/
|
||||
- ✅ 10 chat модулей в components/chat/
|
||||
- ✅ Разделение abstracts
|
||||
|
||||
## ⚠️ Проблемы и недостатки (3 балла снижения)
|
||||
|
||||
### 🔴 Критические проблемы
|
||||
|
||||
#### 1. Дублирование импортов компонентов (CRITICAL)
|
||||
|
||||
**Проблема:** Многие компоненты импортируются дважды:
|
||||
- Один раз в `main.scss` → компилируется в `app.css`
|
||||
- Второй раз в `custom-bootstrap.scss` → компилируется в `bootstrap-custom.css`
|
||||
|
||||
**Дублированные импорты:**
|
||||
```scss
|
||||
// В main.scss:
|
||||
@import 'components/common';
|
||||
@import 'components/navbar';
|
||||
@import 'components/sidebar';
|
||||
@import 'components/page-header';
|
||||
@import 'components/cards';
|
||||
@import 'components/modals';
|
||||
@import 'components/comments';
|
||||
|
||||
// И ЕЩЕ РАЗ в custom-bootstrap.scss:
|
||||
@import "components/common";
|
||||
@import "components/navbar";
|
||||
@import "components/sidebar";
|
||||
@import "components/page-header";
|
||||
@import "components/cards";
|
||||
@import "components/modals";
|
||||
@import "components/comments";
|
||||
```
|
||||
|
||||
**Последствия:**
|
||||
- ❌ Эти стили дублируются в `app.css` И `bootstrap-custom.css`
|
||||
- ❌ Увеличенный размер файлов
|
||||
- ❌ Повторяющиеся селекторы
|
||||
- ❌ Потенциальные конфликты специфичности
|
||||
|
||||
**Затронутые компоненты:**
|
||||
1. common
|
||||
2. navbar
|
||||
3. sidebar
|
||||
4. page-header
|
||||
4. cards
|
||||
5. badges-bem
|
||||
6. modals
|
||||
7. comments
|
||||
|
||||
**Размер дублирования:** ~1,500-2,000 строк SCSS дублируются!
|
||||
|
||||
#### 2. Chat компоненты ТОЛЬКО в bootstrap-custom.css
|
||||
|
||||
**Проблема:** Все 10 chat компонентов импортируются ТОЛЬКО в `custom-bootstrap.scss`:
|
||||
|
||||
```scss
|
||||
// Чат компоненты (в подпапке chat/)
|
||||
@import "components/chat/message-inputs";
|
||||
@import "components/chat/chat-detail";
|
||||
@import "components/chat/chat-date-groups";
|
||||
@import "components/chat/chat-list-enhanced";
|
||||
@import "components/chat/chat-enhanced";
|
||||
@import "components/chat/chat-polls";
|
||||
@import "components/chat/chat-new-messages-indicator";
|
||||
@import "components/chat/message-selection";
|
||||
@import "components/chat/message-context-menu";
|
||||
@import "components/chat/message-reactions";
|
||||
```
|
||||
|
||||
**Последствия:**
|
||||
- ❌ Chat стили попадают в `bootstrap-custom.css` (301 KB)
|
||||
- ❌ Нелогично - chat не относится к Bootstrap
|
||||
- ❌ Сложнее поддерживать - непонятно где искать
|
||||
|
||||
#### 3. Устаревший TODO комментарий
|
||||
|
||||
В `custom-bootstrap.scss` строки 197-199:
|
||||
```scss
|
||||
// Остальные компоненты (загружаются напрямую из CSS - не мигрировано)
|
||||
// TODO: Мигрировать buttons, card-list, document-list, request-list,
|
||||
// ios-search, feed-specific, base-app, request-detail
|
||||
```
|
||||
|
||||
**Проблема:** Эти компоненты УЖЕ мигрированы и импортируются в `main.scss`!
|
||||
|
||||
### 🟡 Средние проблемы
|
||||
|
||||
#### 4. Пустая папка utilities/
|
||||
|
||||
```
|
||||
scss/utilities/ - ПУСТО
|
||||
```
|
||||
|
||||
В `main.scss` закомментированы импорты:
|
||||
```scss
|
||||
// @import 'utilities/spacing-utils';
|
||||
// @import 'utilities/index';
|
||||
```
|
||||
|
||||
**Проблема:**
|
||||
- Папка существует но не используется
|
||||
- spacing-utils лежит в components/, а не utilities/
|
||||
- Запутанная структура
|
||||
|
||||
#### 5. Нет главного README.md в scss/
|
||||
|
||||
**Отсутствует:** `backend/static/scss/README.md`
|
||||
|
||||
Есть только:
|
||||
- `components/BUTTONS_README.md` - документация одного компонента
|
||||
- Нет общего описания архитектуры
|
||||
|
||||
#### 6. Layout компоненты в custom-bootstrap.scss
|
||||
|
||||
```scss
|
||||
// Layout
|
||||
@import "layout/chat-layout";
|
||||
```
|
||||
|
||||
И в main.scss:
|
||||
```scss
|
||||
@import 'layout/base-app';
|
||||
@import 'layout/chat-layout'; // ДУБЛЬ!
|
||||
```
|
||||
|
||||
**chat-layout импортируется дважды!**
|
||||
|
||||
### 🟢 Мелкие замечания
|
||||
|
||||
#### 7. Несогласованность комментариев
|
||||
|
||||
В `main.scss`:
|
||||
```scss
|
||||
// 4. UTILITIES - Вспомогательные классы
|
||||
// @import 'utilities/spacing-utils';
|
||||
// @import 'utilities/index';
|
||||
```
|
||||
|
||||
Но `_spacing-utils.scss` находится в `components/`, не в `utilities/`!
|
||||
|
||||
#### 8. Размер custom-bootstrap.scss
|
||||
|
||||
202 строки, из них:
|
||||
- ~70 строк - переопределения переменных Bootstrap
|
||||
- ~30 строк - импорты компонентов (многие дублируются)
|
||||
- ~100 строк - комментарии
|
||||
|
||||
Файл становится перегруженным.
|
||||
|
||||
## 📈 Статистика
|
||||
|
||||
### Размеры
|
||||
- **Всего SCSS файлов:** 51
|
||||
- **Всего строк SCSS:** 11,394
|
||||
- **Компонентов в components/:** 30
|
||||
- **Компонентов в components/chat/:** 10
|
||||
- **Layout файлов:** 2
|
||||
- **Abstracts файлов:** 4
|
||||
|
||||
### Импорты
|
||||
- **Импортов в main.scss:** 30
|
||||
- **Импортов в custom-bootstrap.scss:** 25
|
||||
- **Дублированных импортов:** 8 компонентов
|
||||
|
||||
### Compiled размеры
|
||||
- **app.css:** 95 KB
|
||||
- **bootstrap-custom.css:** 301 KB
|
||||
- **Дубликаты в обоих:** ~15-20 KB
|
||||
|
||||
## 🎯 Рекомендации по улучшению
|
||||
|
||||
### Приоритет 1: Устранить дублирование (CRITICAL)
|
||||
|
||||
#### Решение A: Разделить компоненты по назначению
|
||||
|
||||
**app.css** (main.scss):
|
||||
- Все application-specific компоненты
|
||||
- Layout (base-app, chat-layout)
|
||||
- Components (buttons, cards, modals, etc.)
|
||||
- Chat components
|
||||
- Spacing utilities
|
||||
|
||||
**bootstrap-custom.css** (custom-bootstrap.scss):
|
||||
- ТОЛЬКО переопределения Bootstrap переменных
|
||||
- ТОЛЬКО импорт Bootstrap
|
||||
- CSS variables (abstracts/css-variables)
|
||||
- Убрать ВСЕ импорты компонентов
|
||||
|
||||
#### Решение B: Создать отдельный chat.css
|
||||
|
||||
**chat.css** (новый entry point):
|
||||
- Все chat компоненты
|
||||
- Chat layout
|
||||
- Подключать только на страницах чата
|
||||
|
||||
### Приоритет 2: Очистить custom-bootstrap.scss
|
||||
|
||||
**Удалить из custom-bootstrap.scss:**
|
||||
```scss
|
||||
// ❌ УДАЛИТЬ ВСЕ ЭТО:
|
||||
@import "layout/chat-layout";
|
||||
@import "components/common";
|
||||
@import "components/navbar";
|
||||
@import "components/sidebar";
|
||||
@import "components/page-header";
|
||||
@import "components/cards";
|
||||
@import "components/badges-bem";
|
||||
@import "components/chat/..."; // все 10 импортов
|
||||
@import "components/comments";
|
||||
@import "components/modals";
|
||||
```
|
||||
|
||||
**Оставить только:**
|
||||
```scss
|
||||
// 1. Переопределение Bootstrap переменных
|
||||
$spacer: 1rem;
|
||||
$spacers: (...);
|
||||
// и т.д.
|
||||
|
||||
// 2. Импорт наших abstracts
|
||||
@import "abstracts/variables";
|
||||
@import "abstracts/mixins";
|
||||
|
||||
// 3. Импорт Bootstrap
|
||||
@import "../node_modules/bootstrap/scss/bootstrap";
|
||||
|
||||
// 4. CSS переменные
|
||||
@import "abstracts/css-variables";
|
||||
|
||||
// ВСЕ! Никаких компонентов!
|
||||
```
|
||||
|
||||
### Приоритет 3: Переместить spacing-utils
|
||||
|
||||
```bash
|
||||
# Переместить из components/ в utilities/
|
||||
mv scss/components/_spacing-utils.scss scss/utilities/
|
||||
mv scss/components/_layout-spacing.scss scss/utilities/
|
||||
```
|
||||
|
||||
Раскомментировать в main.scss:
|
||||
```scss
|
||||
// 4. UTILITIES - Вспомогательные классы
|
||||
@import 'utilities/layout-spacing';
|
||||
@import 'utilities/spacing-utils';
|
||||
```
|
||||
|
||||
### Приоритет 4: Документация
|
||||
|
||||
Создать `scss/README.md`:
|
||||
```markdown
|
||||
# SCSS Architecture
|
||||
|
||||
## Structure
|
||||
- abstracts/ - Variables, mixins, functions
|
||||
- layout/ - Page layouts
|
||||
- components/ - Reusable components
|
||||
- utilities/ - Utility classes
|
||||
|
||||
## Build
|
||||
- main.scss → app.css (application styles)
|
||||
- bootstrap-custom.scss → bootstrap-custom.css (Bootstrap only)
|
||||
|
||||
## Components
|
||||
[список всех компонентов]
|
||||
```
|
||||
|
||||
### Приоритет 5: Удалить устаревшие TODO
|
||||
|
||||
Удалить из custom-bootstrap.scss:
|
||||
```scss
|
||||
// Остальные компоненты (загружаются напрямую из CSS - не мигрировано)
|
||||
// TODO: Мигрировать buttons, card-list, document-list, request-list,
|
||||
// ios-search, feed-specific, base-app, request-detail
|
||||
```
|
||||
|
||||
Все эти компоненты УЖЕ в main.scss!
|
||||
|
||||
## 📋 План действий (по шагам)
|
||||
|
||||
### Шаг 1: Backup текущего состояния
|
||||
```bash
|
||||
cp scss/custom-bootstrap.scss scss/custom-bootstrap.scss.backup
|
||||
cp scss/main.scss scss/main.scss.backup
|
||||
```
|
||||
|
||||
### Шаг 2: Очистить custom-bootstrap.scss
|
||||
Удалить все импорты компонентов, оставить только:
|
||||
- Переопределения переменных Bootstrap
|
||||
- Импорт abstracts
|
||||
- Импорт Bootstrap
|
||||
- Импорт css-variables
|
||||
|
||||
### Шаг 3: Переместить chat в main.scss
|
||||
Добавить в main.scss все chat компоненты:
|
||||
```scss
|
||||
// Chat компоненты
|
||||
@import 'components/chat/message-inputs';
|
||||
@import 'components/chat/chat-detail';
|
||||
// ... все 10 компонентов
|
||||
```
|
||||
|
||||
### Шаг 4: Переместить spacing в utilities
|
||||
```bash
|
||||
mv scss/components/_spacing-utils.scss scss/utilities/
|
||||
mv scss/components/_layout-spacing.scss scss/utilities/
|
||||
```
|
||||
|
||||
### Шаг 5: Обновить импорты в main.scss
|
||||
```scss
|
||||
// Utilities
|
||||
@import 'utilities/layout-spacing';
|
||||
@import 'utilities/spacing-utils';
|
||||
```
|
||||
|
||||
### Шаг 6: Пересобрать CSS
|
||||
```bash
|
||||
npm run build:app
|
||||
npm run build:bootstrap
|
||||
```
|
||||
|
||||
### Шаг 7: Проверить размеры
|
||||
```bash
|
||||
ls -lh css/app.css css/bootstrap-custom.css
|
||||
```
|
||||
|
||||
**Ожидаемый результат:**
|
||||
- app.css: увеличится на ~15-20 KB (добавятся chat компоненты)
|
||||
- bootstrap-custom.css: уменьшится на ~15-20 KB (удалятся дубли)
|
||||
|
||||
### Шаг 8: Создать документацию
|
||||
Создать `scss/README.md` с описанием архитектуры
|
||||
|
||||
### Шаг 9: Тестирование
|
||||
1. Проверить главную страницу
|
||||
2. Проверить страницы с чатом
|
||||
3. Проверить все компоненты
|
||||
|
||||
## 📊 Ожидаемые улучшения
|
||||
|
||||
После рефакторинга:
|
||||
|
||||
### До:
|
||||
```
|
||||
app.css: 95 KB (без chat, с дублями)
|
||||
bootstrap-custom.css: 301 KB (с chat, с дублями)
|
||||
Дублирование: ~15-20 KB
|
||||
```
|
||||
|
||||
### После:
|
||||
```
|
||||
app.css: ~110 KB (с chat, без дублей)
|
||||
bootstrap-custom.css: ~280 KB (только Bootstrap, без дублей)
|
||||
Дублирование: 0 KB
|
||||
Экономия: ~6-7% от общего размера
|
||||
```
|
||||
|
||||
### Преимущества:
|
||||
- ✅ Нет дубликатов кода
|
||||
- ✅ Чистое разделение ответственности
|
||||
- ✅ Логичная структура
|
||||
- ✅ Легче поддерживать
|
||||
- ✅ Меньший итоговый размер
|
||||
|
||||
## 🎓 Best Practices нарушения
|
||||
|
||||
### ❌ Текущие нарушения:
|
||||
|
||||
1. **DRY (Don't Repeat Yourself)** - дублирование 8 компонентов
|
||||
2. **Separation of Concerns** - chat компоненты в bootstrap файле
|
||||
3. **Single Responsibility** - custom-bootstrap делает слишком много
|
||||
4. **Clear Architecture** - utilities/ пуста, spacing в components/
|
||||
|
||||
### ✅ После рефакторинга:
|
||||
|
||||
1. **DRY** - каждый компонент импортируется один раз
|
||||
2. **Separation of Concerns** - четкое разделение: Bootstrap отдельно, app отдельно
|
||||
3. **Single Responsibility** - каждый файл имеет одну четкую цель
|
||||
4. **Clear Architecture** - логичная структура папок
|
||||
|
||||
## 💡 Дополнительные рекомендации
|
||||
|
||||
### 1. Рассмотреть переход на @use/@forward
|
||||
SCSS @import deprecated, современный подход:
|
||||
```scss
|
||||
@use 'abstracts/variables' as vars;
|
||||
@use 'components/buttons';
|
||||
```
|
||||
|
||||
### 2. Создать index.scss в папках
|
||||
```scss
|
||||
// components/chat/_index.scss
|
||||
@forward 'message-inputs';
|
||||
@forward 'chat-detail';
|
||||
// ...
|
||||
|
||||
// Использование:
|
||||
@import 'components/chat'; // импортирует все
|
||||
```
|
||||
|
||||
### 3. Добавить linter
|
||||
```bash
|
||||
npm install -D stylelint stylelint-config-sass-guidelines
|
||||
```
|
||||
|
||||
### 4. Создать style guide
|
||||
Документировать:
|
||||
- Naming conventions
|
||||
- File organization
|
||||
- Import order
|
||||
- Variables usage
|
||||
|
||||
## 🏁 Заключение
|
||||
|
||||
**Текущая архитектура:** 7/10 - Хорошая база, но есть критические проблемы
|
||||
|
||||
**Основные проблемы:**
|
||||
1. Дублирование импортов (CRITICAL)
|
||||
2. Chat компоненты в bootstrap-custom (HIGH)
|
||||
3. Неиспользуемая utilities/ папка (MEDIUM)
|
||||
|
||||
**Действия:**
|
||||
1. Срочно устранить дублирование
|
||||
2. Переорганизовать структуру
|
||||
3. Создать документацию
|
||||
|
||||
**После рефакторинга ожидается:** 9/10 - Чистая, поддерживаемая архитектура
|
||||
|
||||
---
|
||||
|
||||
**Дата:** 15 января 2026
|
||||
**Статус:** Требуется рефакторинг
|
||||
**Приоритет:** HIGH
|
||||
1412
docs/reports/SCSS_REORGANIZATION_PLAN.md
Normal file
1412
docs/reports/SCSS_REORGANIZATION_PLAN.md
Normal file
File diff suppressed because it is too large
Load Diff
Loading…
x
Reference in New Issue
Block a user