Вход — кнопка «Экран» в тулбаре, теперь видна и на мобильном (по аналогии с десктопом, а не спрятана в шторку настроек). В полноэкранном режиме топбар и тулбар выходят из потока и лежат оверлеем поверх сцены: тап/клик по сцене вне элементов управления показывает их, сами прячутся через несколько секунд бездействия; на десктопе дополнительно — наведение мыши в нижнюю полосу экрана. Работает одинаково на мобильном и десктопе. Кнопка настроек устройств в тулбаре на мобильном подписана «Настройки» вместо «Устройства». Раскладка кнопок мобильного тулбара, когда они не помещаются в один ряд (7+, обычный случай с «Экраном» и «Очередью» у организатора), стала равномерной сеткой на 4 колонки вместо переноса «как получится» через flex-wrap.
76 lines
3.5 KiB
TypeScript
76 lines
3.5 KiB
TypeScript
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>
|
||
)
|
||
}
|