/
pvldchkv
/
unity
Обзор
Документация
Войти
/
pvldchkv
/
unity
Код
Запросы
0
Задачи
Вики
Пакеты
0
Релизы
0
CI/CD
Аналитика
Безопасность
master
src/clickstream/EventsToastStack.tsx
112 строк
3 KB
Dyachkov Pavel
Add clickstream analytics infrastructure and mark up pages
29 апр 2026, 16:07
29 апр 2026, 16:07
9e448ec
Код
Авторство
О чём код?
import { useState } from "react"; import type { ClickstreamEvent, ClickstreamKind } from "./types"; export interface ClickstreamToastItem { id: number; kind: ClickstreamKind; event: ClickstreamEvent; } const COPIED_FEEDBACK_MS = 1500; async function copyToClipboard(text: string): Promise<boolean> { // Современный путь — Clipboard API. Доступен на https и localhost. if (navigator.clipboard?.writeText) { try { await navigator.clipboard.writeText(text); return true; } catch { // fall through к фолбэку } } // Фолбэк через скрытый textarea — на случай отказа Clipboard API. try { const textarea = document.createElement("textarea"); textarea.value = text; textarea.setAttribute("readonly", ""); textarea.style.position = "fixed"; textarea.style.opacity = "0"; document.body.appendChild(textarea); textarea.select(); const ok = document.execCommand("copy"); document.body.removeChild(textarea); return ok; } catch { return false; } } interface EventsToastStackProps { toasts: ClickstreamToastItem[]; onDismiss: (id: number) => void; } // Стек тостов, который видно в правом нижнем углу, // когда в dev-tools включён тумблер «Показывать события». // // Сами тосты — dev-only сurface для верификации разметки. // В продакшен-сборке этого UI быть не должно (флаг по умолчанию выключен). export function EventsToastStack({ toasts, onDismiss }: EventsToastStackProps) { if (toasts.length === 0) return null; return ( <div className="clickstream-toast-stack" aria-live="polite"> {toasts.map((t) => ( <ClickstreamToastCard key={t.id} item={t} onDismiss={() => onDismiss(t.id)} /> ))} </div> ); } function ClickstreamToastCard({ item, onDismiss, }: { item: ClickstreamToastItem; onDismiss: () => void; }) { const { kind, event } = item; const json = JSON.stringify(event, null, 2); const [copied, setCopied] = useState(false); const handleCopy = async () => { const ok = await copyToClipboard(json); if (!ok) return; setCopied(true); window.setTimeout(() => setCopied(false), COPIED_FEEDBACK_MS); }; return ( <div className={`clickstream-toast clickstream-toast-${kind}`} role="status"> <div className="clickstream-toast-head"> <span className={`clickstream-toast-kind clickstream-toast-kind-${kind}`}> {kind} </span> <span className="clickstream-toast-label" title={event.label}> {event.label} </span> <button type="button" className={`clickstream-toast-action${copied ? " is-copied" : ""}`} aria-label="Скопировать JSON события" onClick={handleCopy} > {copied ? "Скопировано" : "Копировать"} </button> <button type="button" className="clickstream-toast-close" aria-label="Закрыть" onClick={onDismiss} > × </button> </div> <pre className="clickstream-toast-body">{json}</pre> </div> ); }