Files
vidconf/frontend/src/components/room/RoomTopbar.tsx
Max Ronzhin ed6f9fff44 feat(room): полноэкранный режим на мобильном с выезжающим тулбаром
Вход — кнопка «Экран» в тулбаре, теперь видна и на мобильном (по аналогии
с десктопом, а не спрятана в шторку настроек). В полноэкранном режиме
топбар и тулбар выходят из потока и лежат оверлеем поверх сцены: тап/клик
по сцене вне элементов управления показывает их, сами прячутся через
несколько секунд бездействия; на десктопе дополнительно — наведение мыши
в нижнюю полосу экрана. Работает одинаково на мобильном и десктопе.

Кнопка настроек устройств в тулбаре на мобильном подписана «Настройки»
вместо «Устройства». Раскладка кнопок мобильного тулбара, когда они не
помещаются в один ряд (7+, обычный случай с «Экраном» и «Очередью» у
организатора), стала равномерной сеткой на 4 колонки вместо переноса
«как получится» через flex-wrap.
2026-08-04 00:00:45 +03:00

76 lines
3.5 KiB
TypeScript
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.
import { Check, Copy, Users } from 'lucide-react'
import { useParticipants } from '@livekit/components-react'
import { useState } from 'react'
import { pluralizeParticipants } from '@/lib/pluralize'
interface RoomTopbarProps {
/**
* Название конференции (`ConferenceOut.title`/`ResolveOut.title`) —
* `null`/не задано, если организатор его не указал. НЕ путать с
* `JoinOut.room_name` (техническое имя комнаты LiveKit вида `hC-Dmos9KEM`,
* которое сюда раньше и попадало по ошибке, см. задачу A6) — тот нужен
* только серверу и в UI показывать нечего.
*/
title: string | null
/** Slug/номер конференции из адреса — для инвайт-чипа (копирование ссылки). */
slug?: string
number?: string
/**
* Полноэкранный режим (`RoomPage.tsx`) прячет топбар в оверлей и показывает
* его только по этому флагу — вне полноэкранного режима не влияет ни на что
* (CSS-правило само по себе действует лишь под `.room-fullscreen-overlay`).
*/
overlayVisible?: boolean
}
/**
* Верхняя панель комнаты: название, число участников и инвайт-чип
* (номер + копирование ссылки-приглашения `/j/:slug`).
*
* Примечание: в design/mockups/room.html этого чипа нет — он добавлен,
* чтобы ссылка и номер конференции были видны и копировались, в стиле
* тёмных токенов темы `room` (см. `--color-room-tile*`), без новых
* цветов и форм.
*/
export function RoomTopbar({ title, slug, number, overlayVisible }: RoomTopbarProps) {
const participants = useParticipants()
const [copied, setCopied] = useState(false)
async function handleCopy() {
if (!slug) return
const link = `${window.location.origin}/j/${slug}`
try {
await navigator.clipboard.writeText(link)
setCopied(true)
setTimeout(() => setCopied(false), 2000)
} catch {
// Буфер обмена недоступен (нет разрешения/не https) — молча игнорируем,
// это некритичный вспомогательный элемент интерфейса.
}
}
return (
<header className={`room-topbar${overlayVisible ? ' is-visible' : ''}`}>
<div className="room-title-block">
<h1>{title ?? 'Конференция без названия'}</h1>
<p>
<span className="rec-dot" aria-hidden="true" />
Конференция активна · <Users className="lucide" style={{ width: 14, height: 14 }} aria-hidden="true" />{' '}
{participants.length} {pluralizeParticipants(participants.length)}
</p>
</div>
{slug && (
<button type="button" className="room-invite-chip" onClick={handleCopy}>
<span>{number ? `${number}` : slug}</span>
{copied ? (
<Check style={{ width: 14, height: 14 }} aria-hidden="true" />
) : (
<Copy style={{ width: 14, height: 14 }} aria-hidden="true" />
)}
{copied ? 'Скопировано' : 'Пригласить'}
</button>
)}
</header>
)
}