Files
vidconf/frontend/src/styles/calendar.css
Max Ronzhin 66935b04f8 refactor(calendar): чип события — только название и карандашик, заголовок периода без «г.»
В renderEventContent убраны бейдж статуса и время (переехали в
ConferenceOccurrenceDialog) — остались название с обрезкой по многоточию
(min-width:0/overflow:hidden по цепочке флекс-контейнеров, чинит вылезание
текста за границы ячейки) и, у владельца конференции, карандашик
редактирования (myConferenceById/onEditClick, stopPropagation, чтобы не
срабатывал клик по чипу). Клик по чипу — всегда информационное окно, включая
свои конференции.

titleFormat для dayGridWeek/dayGridMonth построен на Intl.DateTimeFormat
вместо ruLocale-форматтера — тот всегда добавляет «г.» после года.
2026-07-26 23:34:49 +03:00

298 lines
19 KiB
CSS
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/*
* VidConf — стили экрана календаря конференций: менеджер
* запланированных/закреплённых конференций (переговорных комнат и их
* занятости в модели нет). Базовая раскладка/токены — 1:1 из design/mockups/calendar.html
* (.layout-grid, .week-card, .create-card, .pin-block, .recurrence-panel).
* Тема FullCalendar адаптирована под dayGrid-«чипы» вместо timeGrid-слотов
* (см. комментарий в ConferenceCalendar.tsx).
*/
/* ---------- Календарь во всю ширину контента ---------- */
/* Раньше `.layout-grid` была двухколоночной сеткой (календарь + вечно
висящая сбоку `.create-card`) — форма создания/редактирования переехала в
модалку (`.modal-form-wrap`, см. my-conferences.css), сайдбара больше нет.
`min-width:0` оставлен: `.week-card` внутри — единственный элемент грида, и
без этого грид-трек не сжимается уже своего min-content (внутренняя таблица
FullCalendar с 5 колонками) — классический grid blowout, раздувающий трек и
всю страницу шире вьюпорта (см. тот же приём в lobby.css/.action). */
.layout-grid {
display: grid;
grid-template-columns: 1fr;
gap: var(--space-6);
align-items: start;
}
.layout-grid > * { min-width: 0; }
.week-card {
background: var(--color-surface);
border: 1px solid var(--color-border);
border-radius: var(--radius-2xl);
padding: var(--space-6);
}
.week-toolbar { display: flex; align-items: center; justify-content: space-between; margin-bottom: var(--space-5); flex-wrap: wrap; gap: var(--space-3); }
.date-nav { display: flex; align-items: center; gap: var(--space-3); }
.date-nav-arrow {
width: 34px;
height: 34px;
flex-shrink: 0;
border-radius: 50%;
border: 1px solid var(--color-border);
background: var(--color-surface);
color: var(--color-ink-500);
display: flex;
align-items: center;
justify-content: center;
}
.date-nav-arrow:hover { border-color: var(--color-border-strong); color: var(--color-ink-900); }
.date-nav span { font: var(--text-h3); color: var(--color-ink-900); min-width: 200px; text-align: center; text-transform: capitalize; }
.date-nav .btn-secondary { flex-shrink: 0; white-space: nowrap; }
.view-toggle { display: flex; gap: 4px; background: var(--color-bg-alt); border: 1px solid var(--color-border); border-radius: var(--radius-full); padding: 4px; }
.view-toggle button { border: none; background: transparent; border-radius: var(--radius-full); padding: 7px 16px; font: var(--text-body); font-weight: 600; color: var(--color-ink-500); }
.view-toggle button.is-active { background: var(--color-surface); color: var(--color-ink-700); box-shadow: var(--shadow-sm); }
/* ---------- Тема FullCalendar (dayGrid) ---------- */
.vc-calendar { font-family: var(--font-body); }
.vc-calendar .fc { --fc-border-color: var(--color-border); --fc-page-bg-color: var(--color-surface); --fc-neutral-bg-color: var(--color-bg-alt); --fc-today-bg-color: rgba(212,242,227,.16); }
.vc-calendar .fc-theme-standard td, .vc-calendar .fc-theme-standard th { border-color: var(--color-border); }
.vc-calendar .fc-col-header-cell { background: var(--color-bg-alt); padding: 6px 0; }
.vc-calendar .fc-col-header-cell-cushion { font: var(--text-caption); color: var(--color-ink-500); text-decoration: none; padding: 6px 0; }
.vc-calendar .fc-daygrid-day-frame { min-height: 460px; padding: var(--space-2); }
.vc-calendar .fc-daygrid-day-number { font: var(--text-h3); color: var(--color-ink-900); padding: 4px 6px; }
.vc-calendar .fc-day-today { background: rgba(212,242,227,.16) !important; }
.vc-calendar .fc-day-today .fc-daygrid-day-number { color: var(--color-ink-700); }
.vc-calendar .fc-daygrid-day-events { margin: 0; display: flex; flex-direction: column; gap: var(--space-2); }
.vc-calendar .fc-daygrid-event-harness { margin: 0 !important; }
.vc-calendar .fc-daygrid-more-link { font: var(--text-caption); color: var(--color-ink-500); }
/*
* Событие-вхождение конференции — карточка-«чип» в духе .conf-chip мокапа.
* FullCalendar оборачивает событие в .fc-daygrid-event (наследует классы из
* `classNames`) — переопределяем его внешний вид под чип целиком, без
* отдельного контейнера. Содержимое — только название (+карандашик у
* владельца, время/статус/номер — в ConferenceOccurrenceDialog), поэтому чип —
* одна строка (flex row), а не колонка бейдж/время/название, как раньше.
*/
.vc-calendar .fc-daygrid-event.conf-chip {
display: flex !important;
align-items: center;
gap: 8px;
min-width: 0;
background: var(--color-surface) !important;
border: 1px solid var(--color-border) !important;
border-radius: var(--radius-md) !important;
padding: var(--space-3);
white-space: normal;
cursor: pointer;
transition: box-shadow var(--transition-fast), border-color var(--transition-fast);
}
.vc-calendar .fc-daygrid-event.conf-chip:hover { box-shadow: var(--shadow-sm); border-color: var(--color-border-strong); }
.vc-calendar .fc-daygrid-event.conf-chip .fc-event-time,
.vc-calendar .fc-daygrid-event.conf-chip .fc-event-title { display: none; } /* заменено собственным eventContent */
/* Цепочка min-width:0 + overflow:hidden + text-overflow:ellipsis — без неё
длинное название распирает чип и вылезает за границы ячейки (в узкой
недельной колонке — особенно). `.fc-daygrid-event-harness` — прямой
flex-item `.fc-daygrid-day-events` (флекс-колонка) выше по файлу: по
умолчанию у флекс-айтема min-width:auto (= min-content), из-за чего он не
сжимается уже ширины своего содержимого — тот самый grid/flex-blowout. */
.vc-calendar .fc-daygrid-event-harness { min-width: 0; }
.chip-row { display: flex; align-items: center; gap: 8px; min-width: 0; flex: 1; }
.chip-title { display: flex; align-items: center; gap: 6px; font: var(--text-body); font-weight: 700; color: var(--color-ink-900); min-width: 0; flex: 1; }
.chip-title-text { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; min-width: 0; }
.chip-lock { width: 13px; height: 13px; flex-shrink: 0; }
.chip-edit {
width: 22px;
height: 22px;
flex-shrink: 0;
border-radius: 50%;
border: none;
background: transparent;
color: var(--color-ink-500);
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
}
.chip-edit svg { width: 13px; height: 13px; }
.chip-edit:hover { background: var(--color-bg-alt); color: var(--color-ink-900); }
.conf-badge { display: inline-flex; align-items: center; gap: 4px; padding: 2px 9px; border-radius: var(--radius-full); font: var(--text-caption); font-size: 11px; width: fit-content; }
.conf-badge.scheduled { background: var(--color-warning-bg); color: var(--color-warning); }
.conf-badge.pinned { background: var(--color-bg-alt); color: var(--color-ink-700); border: 1px solid var(--color-border-strong); }
.conf-badge.live { background: var(--color-success-bg); color: var(--color-success); }
.conf-badge.ended { background: var(--color-bg-alt); color: var(--color-ink-500); }
.vc-empty-hint { text-align: center; padding: var(--space-8); color: var(--color-ink-500); font: var(--text-body-lg); }
/* ---------- Карточка создания/редактирования конференции ---------- */
.create-card {
background: var(--color-surface);
border: 1px solid var(--color-border);
border-radius: var(--radius-2xl);
padding: var(--space-6);
position: sticky;
top: 96px;
}
.create-card h2 { font: var(--text-h2); color: var(--color-ink-900); margin: 0 0 var(--space-5); display: flex; align-items: center; gap: 8px; }
.field-row { display: flex; gap: var(--space-3); }
.field-row .field { flex: 1; }
.next-occurrence { display: flex; align-items: center; gap: 8px; background: var(--color-bg-alt); border-radius: var(--radius-md); padding: 10px 14px; font: var(--text-body); color: var(--color-ink-700); font-weight: 600; margin-bottom: var(--space-5); }
/* Тумблер (используется и для «закрытая по паролю», и для «закрепить») */
.toggle-row { display: flex; align-items: center; justify-content: space-between; gap: var(--space-3); padding: var(--space-3) 0; border-top: 1px solid var(--color-border); }
.toggle-copy strong { display: block; font: var(--text-body); color: var(--color-ink-900); }
.toggle-copy span { font: 12px var(--font-body); color: var(--color-ink-500); }
.switch { position: relative; width: 44px; height: 24px; flex-shrink: 0; }
.switch input { opacity: 0; width: 0; height: 0; position: absolute; }
.slider { position: absolute; inset: 0; background: var(--color-border-strong); border-radius: var(--radius-full); transition: background var(--transition-fast); cursor: pointer; }
.slider::before { content:""; position:absolute; width:18px; height:18px; left:3px; top:3px; background:#fff; border-radius:50%; transition: transform var(--transition-fast); }
.switch input:checked + .slider { background: var(--color-accent-active); }
.switch input:checked + .slider::before { transform: translateX(20px); }
.switch input:focus-visible + .slider { outline: 2px solid var(--color-focus-ring); outline-offset: 2px; }
/* Disabled-переключатель (напр. анонс записи конференций в «Модулях»
админки) — приглушение вместо hover/checked-цвета. */
.switch input:disabled + .slider { opacity: .5; cursor: not-allowed; }
.password-block { margin-top: var(--space-4); }
/* ---------- Блок повторений (recurrence) ---------- */
.recurrence-panel {
display: flex;
flex-direction: column;
gap: var(--space-2);
margin-top: var(--space-4);
padding-top: var(--space-4);
border-top: 1px dashed var(--color-border);
}
.recurrence-option-row {
display: flex; align-items: center; gap: 10px;
padding: 10px 12px; border: 1px solid var(--color-border); border-radius: var(--radius-md);
cursor: pointer; font: var(--text-body); font-weight: 600; color: var(--color-ink-900);
transition: border-color var(--transition-fast), background var(--transition-fast);
}
.recurrence-option-row:has(input:checked) { border-color: var(--color-ink-700); background: var(--color-bg-alt); }
.recurrence-option-row input[type="radio"] { width: 16px; height: 16px; accent-color: var(--color-ink-700); flex-shrink: 0; }
.recurrence-sub { padding: var(--space-3) 12px 4px 38px; }
.weekday-toggles { display: flex; gap: 6px; flex-wrap: wrap; }
.weekday-chip {
width: 34px; height: 34px; border-radius: 50%;
border: 1.5px solid var(--color-border); background: var(--color-surface);
font: var(--text-caption); color: var(--color-ink-500);
display: flex; align-items: center; justify-content: center;
cursor: pointer;
}
.weekday-chip.is-selected { background: var(--color-ink-700); border-color: var(--color-ink-700); color: #fff; }
.recurrence-hint { font: 12px var(--font-body); color: var(--color-ink-500); margin-top: 8px; }
/* ---------- Кнопки действий карточки редактирования ---------- */
.btn-row { display: flex; gap: var(--space-3); margin-top: var(--space-5); }
.btn-row .btn { flex: 1; justify-content: center; }
.danger-link-row { text-align: center; margin-top: var(--space-4); }
.btn-text { background: none; border: none; color: var(--color-danger); font: var(--text-body); font-weight: 600; padding: 4px; cursor: pointer; }
.btn-text:hover { text-decoration: underline; }
.btn.full { width: 100%; justify-content: center; margin-top: var(--space-5); }
.btn-danger { background: var(--color-danger); color: #fff; font: var(--text-body); font-weight: 700; padding: 9px 18px; border-radius: var(--radius-full); border: none; cursor: pointer; display: inline-flex; align-items: center; gap: 8px; }
.btn-danger:hover:not(:disabled) { filter: brightness(0.92); }
/* ---------- Пикер участников (ParticipantsPicker) ----------
* Новый элемент без готового макета — собран из утверждённых паттернов:
* чипы как `.chip` фильтров «Моих конференций», выпадающий список результатов
* переиспользует классы дроплиста селекта (`.vc-select-panel`/`.vc-select-option`,
* §4.16), поле поиска — как `.search-wrap` админки.
*/
.participants-picker { position: relative; }
.participants-chips { display: flex; flex-wrap: wrap; gap: 8px; margin-bottom: var(--space-3); }
.participant-chip {
display: inline-flex; align-items: center; gap: 6px;
padding: 4px 10px 4px 4px; border-radius: var(--radius-full);
border: 1px solid var(--color-border); background: var(--color-bg-alt);
font: var(--text-body); font-weight: 600; color: var(--color-ink-900);
}
.participant-chip.is-organizer { padding-right: 12px; border-color: var(--color-border-strong); }
.participant-chip-role { font: var(--text-caption); color: var(--color-ink-500); font-weight: 600; }
.participant-chip-remove {
width: 18px; height: 18px; flex-shrink: 0; border-radius: 50%; border: none;
background: transparent; color: var(--color-ink-400);
display: flex; align-items: center; justify-content: center; cursor: pointer;
}
.participant-chip-remove:hover:not(:disabled) { background: var(--color-border); color: var(--color-ink-900); }
.participant-chip-remove:disabled { cursor: not-allowed; opacity: .5; }
.participants-search-wrap { position: relative; }
.participants-search-wrap .icon { position: absolute; left: 12px; top: 50%; transform: translateY(-50%); color: var(--color-ink-400); }
.participants-search-wrap input {
width: 100%; font: var(--text-body-lg); color: var(--color-ink-900);
padding: 10px 14px 10px 38px; border-radius: var(--radius-md);
border: 1px solid var(--color-border); background: var(--color-surface); font-family: var(--font-body);
}
.participants-search-wrap input:focus { outline: none; border-color: var(--color-focus-ring); box-shadow: 0 0 0 2px var(--color-focus-ring); }
.participants-search-wrap input:disabled { background: var(--color-bg-alt); color: var(--color-ink-300); cursor: not-allowed; }
.participants-results { top: calc(100% + 6px); max-height: 240px; }
.participant-option-body { display: flex; align-items: center; gap: 8px; font: var(--text-body); color: var(--color-ink-900); }
.participants-results-hint { padding: 10px 14px; font: var(--text-body); color: var(--color-ink-500); }
/* ---------- Диалог быстрого просмотра вхождения (ConferenceOccurrenceDialog) ---------- */
.detail-row { display: flex; align-items: flex-start; gap: 10px; padding: 10px 0; border-bottom: 1px solid var(--color-border); font: var(--text-body); color: var(--color-ink-900); }
.detail-row:last-of-type { border-bottom: none; }
.detail-row svg { color: var(--color-ink-400); margin-top: 2px; flex-shrink: 0; }
.detail-row .label { color: var(--color-ink-500); min-width: 90px; flex-shrink: 0; }
.title-lock { width: 15px; height: 15px; margin-right: 6px; flex-shrink: 0; }
/* =====================================================================
* Тёмная тема: точечные патчи (design/mockups/dark/README.md).
* ===================================================================== */
@media (prefers-color-scheme: dark) {
:root:not([data-theme]) .vc-calendar .fc { --fc-today-bg-color: rgba(127, 221, 168, 0.10); }
:root:not([data-theme]) .vc-calendar .fc-day-today { background: rgba(127, 221, 168, 0.10) !important; }
:root:not([data-theme]) .weekday-chip.is-selected { color: var(--color-accent-text); }
:root:not([data-theme]) .btn-danger { background: var(--color-danger-solid); }
:root:not([data-theme]) .btn-danger:hover:not(:disabled) { background: var(--color-danger-solid-hover); filter: none; }
}
:root[data-theme='dark'] .vc-calendar .fc { --fc-today-bg-color: rgba(127, 221, 168, 0.10); }
:root[data-theme='dark'] .vc-calendar .fc-day-today { background: rgba(127, 221, 168, 0.10) !important; }
:root[data-theme='dark'] .weekday-chip.is-selected { color: var(--color-accent-text); }
:root[data-theme='dark'] .btn-danger { background: var(--color-danger-solid); }
:root[data-theme='dark'] .btn-danger:hover:not(:disabled) { background: var(--color-danger-solid-hover); filter: none; }
/* ---------- Узкие экраны ---------- */
/* ВАЖНО: этот блок должен идти строго ПОСЛЕДНИМ в файле — при равной
специфичности с безусловными правилами выше (`.create-card`, `.field-row`
и т.п. — те же 0,1,0/0,1,1) побеждает порядок в исходнике, а не сам факт
совпадения media query (тот же баг уже ловили в lobby.css/.action, гейт 3
— см. комментарий там). Макет calendar.html — desktop-only
(`html { min-width: 1280px }`, DESIGN_SYSTEM.md §«Breakpoints»), адаптива
в нём нет.
.layout-grid уже одноколоночная на всех ширинах (§2.2) — двухколоночного
960px-брейкпоинта под сайдбар-форму больше нет, форма теперь в модалке
(.modal-form-wrap .create-card в my-conferences.css гасит sticky сама).
*/
@media (max-width: 640px) {
.week-card, .create-card { padding: var(--space-4); }
.week-toolbar { flex-direction: column; align-items: stretch; }
.date-nav { justify-content: space-between; }
.date-nav span { min-width: 0; flex: 1; }
/* Недельная сетка FullCalendar (5 рабочих дней) не помещается на ~360px
содержимого карточки без потери читаемости — даём ей прокручиваться
ГОРИЗОНТАЛЬНО ВНУТРИ карточки (min-width задаёт компактную, но ещё
читаемую ширину колонки), а не раздувать всю страницу (см. min-width:0
на .layout-grid выше — здесь наоборот сознательно возвращаем разумный
min-width самому графику, но уже внутри своего overflow-контейнера). */
.vc-calendar { overflow-x: auto; }
.vc-calendar .fc { min-width: 560px; }
.field-row { flex-wrap: wrap; }
.recurrence-sub { padding-left: var(--space-4); }
}