Participants mobile;
This commit is contained in:
@@ -10,7 +10,7 @@ export default function Warning({
|
||||
return (
|
||||
<div
|
||||
className={clsx(
|
||||
"size-[1.111vw] border-[1px] border-white rounded-full flex items-center justify-center ",
|
||||
"2xl:size-[1.111vw] size-[4.444vw] border-[1px] border-white rounded-full flex items-center justify-center ",
|
||||
type === "caution" && "bg-[#F9A530]",
|
||||
type === "critical" && "bg-[#FF4517]",
|
||||
className
|
||||
|
||||
@@ -7,9 +7,12 @@ import XMarkFilledIcon from "../icons/XMarkFilledIcon";
|
||||
import Avatar from "../ui/Avatar";
|
||||
import Button from "../ui/Button";
|
||||
import ShareFilledIcon from "../icons/ShareFilledIcon";
|
||||
import HandRaisedOffFilledIcon from "../icons/HandRaisedOffFilledIcon";
|
||||
import MicrophoneOffFilledIcon from "../icons/MicrophoneOffFilledIcon";
|
||||
import { Fragment, useRef } from "react";
|
||||
|
||||
export default function ParticipantsPopup() {
|
||||
const participants = [1, 2, 3];
|
||||
const participants = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
|
||||
|
||||
return (
|
||||
<PopupWrapper
|
||||
@@ -18,14 +21,14 @@ export default function ParticipantsPopup() {
|
||||
className="h-max 2xl:w-[21.667vw]"
|
||||
>
|
||||
<div className="flex flex-col gap-[1.667vw] relative">
|
||||
<ul className="flex flex-col gap-[1.111vw]">
|
||||
<ul className="flex flex-col gap-0 2xl:gap-[1.111vw] 2xl:max-h-auto max-h-[calc(100dvh-50vw)] overflow-y-auto">
|
||||
{participants.map((participant, index) => (
|
||||
<>
|
||||
<ParticipantItem key={participant} id={participant.toString()} />
|
||||
<Fragment key={index}>
|
||||
<ParticipantItem id={participant.toString()} />
|
||||
{index !== participants.length - 1 && (
|
||||
<div className="w-full h-[1px] bg-[#F6F6F6]" />
|
||||
)}
|
||||
</>
|
||||
</Fragment>
|
||||
))}
|
||||
</ul>
|
||||
<Button
|
||||
@@ -53,9 +56,18 @@ export default function ParticipantsPopup() {
|
||||
}
|
||||
|
||||
function ParticipantItem({ id }: { id: string }) {
|
||||
const isMuted = true;
|
||||
const isNotControlling = true;
|
||||
const isMobile = window.innerWidth <= 360;
|
||||
const parentRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
return (
|
||||
<div className="flex items-center justify-between w-full h-[2.5vw]">
|
||||
<div className="flex items-center gap-[0.833vw]">
|
||||
<div
|
||||
ref={parentRef}
|
||||
onTouchEnd={(ev) => ev.stopPropagation()}
|
||||
className="flex items-center justify-between w-full relative py-[2.222vw] 2xl:p-0"
|
||||
>
|
||||
<div className="flex items-center 2xl:gap-[0.833vw] gap-[3.333vw]">
|
||||
<Avatar size="medium" status="caution" />
|
||||
<div className="flex flex-col gap-[0.278vw]">
|
||||
<span className="button-m">Иван Иванович {id}</span>
|
||||
@@ -63,7 +75,18 @@ function ParticipantItem({ id }: { id: string }) {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<div className="flex 2xl:gap-[0.556vw] gap-[2.222vw] items-center">
|
||||
{isNotControlling && (
|
||||
<div className="2xl:size-[1.111vw] size-[4.444vw] text-[#FF4517]">
|
||||
<HandRaisedOffFilledIcon />
|
||||
</div>
|
||||
)}
|
||||
{isMuted && (
|
||||
<div className="2xl:size-[1.111vw] size-[4.444vw] text-[#FF4517]">
|
||||
<MicrophoneOffFilledIcon />
|
||||
</div>
|
||||
)}
|
||||
|
||||
<ActionsPopover
|
||||
options={[
|
||||
{
|
||||
@@ -88,6 +111,8 @@ function ParticipantItem({ id }: { id: string }) {
|
||||
onClick: () => {},
|
||||
},
|
||||
]}
|
||||
parentRef={isMobile ? parentRef : undefined}
|
||||
className={isMobile ? "left-0" : undefined}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -2,6 +2,7 @@ import { useState, useRef, useEffect } from "react";
|
||||
import Button from "./Button";
|
||||
import MoreIcon from "../icons/MoreIcon";
|
||||
import PopoverWrapper from "./PopoverWrapper";
|
||||
import clsx from "clsx";
|
||||
|
||||
interface ActionsPopoverProps {
|
||||
options: {
|
||||
@@ -10,47 +11,79 @@ interface ActionsPopoverProps {
|
||||
icon?: React.ReactNode;
|
||||
disabled?: boolean;
|
||||
}[];
|
||||
isOpened?: boolean;
|
||||
parentRef?: React.RefObject<HTMLDivElement | null>;
|
||||
className?: string;
|
||||
}
|
||||
|
||||
export default function ActionsPopover({ options }: ActionsPopoverProps) {
|
||||
const [isOpened, setIsOpened] = useState(false);
|
||||
|
||||
/**
|
||||
* @param parentRef - Если передан, то кнопка активации опций не будет отображаться, а сам Popover будет отображаться по клику на parentRef.
|
||||
*/
|
||||
export default function ActionsPopover({
|
||||
options,
|
||||
isOpened = false,
|
||||
parentRef,
|
||||
className,
|
||||
}: ActionsPopoverProps) {
|
||||
const [opened, setOpened] = useState(isOpened);
|
||||
const popoverRef = useRef<HTMLDivElement>(null);
|
||||
const buttonRef = useRef<HTMLButtonElement>(null);
|
||||
|
||||
useEffect(() => {
|
||||
let isHandling = false;
|
||||
|
||||
const handleClickOutside = (event: MouseEvent | TouchEvent) => {
|
||||
if (
|
||||
popoverRef.current &&
|
||||
!popoverRef.current.contains(event.target as Node)
|
||||
) {
|
||||
setIsOpened(false);
|
||||
// Предотвращаем двойное срабатывание mousedown и touchstart
|
||||
if (isHandling) return;
|
||||
isHandling = true;
|
||||
setTimeout(() => {
|
||||
isHandling = false;
|
||||
}, 200);
|
||||
|
||||
const tgt = event.target as Node;
|
||||
const clickedInsideParentElement = parentRef?.current?.contains(tgt);
|
||||
const clickedInsidePopover = popoverRef.current?.contains(tgt);
|
||||
if (clickedInsideParentElement && !clickedInsidePopover) {
|
||||
//
|
||||
} else {
|
||||
// Если нет parentRef - закрытие только по клику вне popover и вне кнопки
|
||||
const clickedInsideButton = buttonRef.current?.contains(tgt);
|
||||
if (!clickedInsidePopover && !clickedInsideButton) {
|
||||
setOpened(false);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
document.addEventListener("mousedown", handleClickOutside);
|
||||
document.addEventListener("touchstart", handleClickOutside);
|
||||
parentRef?.current?.addEventListener("touchend", () => setOpened(!opened));
|
||||
return () => {
|
||||
document.removeEventListener("mousedown", handleClickOutside);
|
||||
document.removeEventListener("touchstart", handleClickOutside);
|
||||
parentRef?.current?.addEventListener("touchend", () =>
|
||||
setOpened(!opened)
|
||||
);
|
||||
};
|
||||
}, []);
|
||||
}, [parentRef]);
|
||||
|
||||
return (
|
||||
<div className="relative" ref={popoverRef}>
|
||||
<button
|
||||
ref={buttonRef}
|
||||
className="flex items-center justify-center gap-[0.139vw] size-[1.667vw] rounded-[0.556vw] text-[#CCCCCC] hover:text-[#7D7D7D] hover:bg-[#F3F3F3] active:text-[#141414] "
|
||||
onClick={() => setIsOpened(!isOpened)}
|
||||
>
|
||||
<div className="2xl:size-[1.111vw] size-4 2xl:rounded-[0.556vw] rounded-2xl">
|
||||
<MoreIcon />
|
||||
</div>
|
||||
</button>
|
||||
<div className={!parentRef ? "relative" : ""} ref={popoverRef}>
|
||||
{!parentRef && (
|
||||
<button
|
||||
ref={buttonRef}
|
||||
className="flex items-center justify-center gap-[0.139vw] size-[1.667vw] rounded-[0.556vw] text-[#CCCCCC] hover:text-[#7D7D7D] hover:bg-[#F3F3F3] active:text-[#141414] "
|
||||
onClick={() => setOpened(!opened)}
|
||||
>
|
||||
<div className="2xl:size-[1.111vw] size-4 2xl:rounded-[0.556vw] rounded-2xl">
|
||||
<MoreIcon />
|
||||
</div>
|
||||
</button>
|
||||
)}
|
||||
|
||||
<PopoverWrapper
|
||||
isOpened={isOpened}
|
||||
buttonRef={buttonRef}
|
||||
className="w-[17.222vw]"
|
||||
isOpened={opened}
|
||||
parentElRef={parentRef ? parentRef : buttonRef}
|
||||
className={clsx("2xl:w-[17.222vw] w-[53.333vw]", className)}
|
||||
>
|
||||
{options.map((option) => (
|
||||
<Button
|
||||
@@ -60,7 +93,9 @@ export default function ActionsPopover({ options }: ActionsPopoverProps) {
|
||||
onClick={option.onClick}
|
||||
disabled={option.disabled}
|
||||
>
|
||||
<div className="size-[1.111vw] ">{option.icon}</div>
|
||||
<div className="2xl:size-[1.111vw] size-[4.444vw]">
|
||||
{option.icon}
|
||||
</div>
|
||||
{option.label}
|
||||
</Button>
|
||||
))}
|
||||
|
||||
@@ -15,18 +15,21 @@ export default function Avatar({ size, status, src, name }: AvatarProps) {
|
||||
<div
|
||||
className={clsx(
|
||||
"rounded-full text-white relative",
|
||||
size === "small" && "size-[2.222vw] button-s",
|
||||
size === "medium" && "size-[2.5vw] button-m",
|
||||
size === "large" && "size-[3.333vw] title-s"
|
||||
size === "small" && "2xl:size-[2.222vw] size-[8.889vw] button-s",
|
||||
size === "medium" && "2xl:size-[2.5vw] size-[10vw] button-m",
|
||||
size === "large" && "2xl:size-[3.333vw] size-[13.333vw] title-s"
|
||||
)}
|
||||
>
|
||||
{GetAvatarImage(src, name)}
|
||||
<div
|
||||
className={clsx(
|
||||
"absolute",
|
||||
size === "small" && "bottom-[1.389vw] left-[1.389vw]",
|
||||
size === "medium" && "bottom-[1.667vw] left-[1.667vw]",
|
||||
size === "large" && "bottom-[2.5vw] left-[2.5vw]"
|
||||
size === "small" &&
|
||||
"2xl:bottom-[1.389vw] bottom-[5.556vw] 2xl:left-[1.389vw] left-[5.556vw]",
|
||||
size === "medium" &&
|
||||
"2xl:bottom-[1.667vw] bottom-[6.667vw] 2xl:left-[1.667vw] left-[6.667vw]",
|
||||
size === "large" &&
|
||||
"2xl:bottom-[2.5vw] bottom-[10vw] 2xl:left-[2.5vw] left-[10vw]"
|
||||
)}
|
||||
>
|
||||
{status === "caution" && (
|
||||
|
||||
@@ -54,7 +54,7 @@ function ControlsPopover() {
|
||||
</FloatingActionButton>
|
||||
<PopoverWrapper
|
||||
isOpened={isOpened}
|
||||
buttonRef={buttonRef}
|
||||
parentElRef={buttonRef}
|
||||
className="w-[248px] !bottom-[72px] min-h-full !fixed left-1/2 -translate-x-1/2"
|
||||
>
|
||||
<Button
|
||||
|
||||
@@ -4,14 +4,14 @@ import clsx from "clsx";
|
||||
|
||||
interface PopoverProps {
|
||||
isOpened: boolean;
|
||||
buttonRef: React.RefObject<HTMLButtonElement | null>;
|
||||
parentElRef: React.RefObject<HTMLButtonElement | HTMLDivElement | null>;
|
||||
children: React.ReactNode;
|
||||
className?: string;
|
||||
}
|
||||
|
||||
function PopoverWrapper({
|
||||
isOpened,
|
||||
buttonRef,
|
||||
parentElRef,
|
||||
children,
|
||||
className,
|
||||
}: PopoverProps) {
|
||||
@@ -19,13 +19,13 @@ function PopoverWrapper({
|
||||
const [menuHeight, setMenuHeight] = useState(0);
|
||||
|
||||
useEffect(() => {
|
||||
if (isOpened && buttonRef.current) {
|
||||
const buttonRect = buttonRef.current.getBoundingClientRect();
|
||||
if (isOpened && parentElRef.current) {
|
||||
const buttonRect = parentElRef.current.getBoundingClientRect();
|
||||
const spaceBelow = window.innerHeight - buttonRect.bottom;
|
||||
const spaceAbove = buttonRect.top;
|
||||
setOpenUpwards(spaceBelow < menuHeight && spaceAbove > menuHeight);
|
||||
}
|
||||
}, [buttonRef, isOpened, menuHeight]);
|
||||
}, [parentElRef, isOpened, menuHeight]);
|
||||
|
||||
return (
|
||||
<AnimatePresence>
|
||||
|
||||
@@ -9,10 +9,11 @@ import useModalStore from "../store/modalStore";
|
||||
import CogFilledIcon from "../components/icons/CogFilledIcon";
|
||||
// import SessionUsersPanel from "../components/SessionUsersPanel";
|
||||
import SharePopup from "../components/popups/SharePopup";
|
||||
import { useEffect } from "react";
|
||||
import useToastsStore from "../store/toastsStore";
|
||||
// import { useEffect } from "react";
|
||||
// import useToastsStore from "../store/toastsStore";
|
||||
import ChatPopup from "../components/popups/ChatPopup";
|
||||
import ChatFilledIcon from "../components/icons/ChatFilledIcon";
|
||||
import ParticipantsPopup from "../components/popups/ParticipantsPopup";
|
||||
|
||||
function HomePage() {
|
||||
const { data: user } = useMe();
|
||||
@@ -27,30 +28,30 @@ function HomePage() {
|
||||
const { setPopup } = usePopupStore();
|
||||
const { setModal } = useModalStore();
|
||||
|
||||
// -------------------------------- Toasts test --------------------------------
|
||||
const { addToast } = useToastsStore();
|
||||
// -------------------------------- Тосты --------------------------------
|
||||
// const { addToast } = useToastsStore();
|
||||
|
||||
useEffect(() => {
|
||||
addToast({
|
||||
id: crypto.randomUUID(),
|
||||
type: "warning",
|
||||
title: "Тестовое предупреждение",
|
||||
message: "Это тестовое предупреждение",
|
||||
onDeny: () => {},
|
||||
onAllow: () => {},
|
||||
});
|
||||
const timer = setTimeout(() => {
|
||||
addToast({
|
||||
id: crypto.randomUUID(),
|
||||
type: "notification",
|
||||
title: "Тестовое уведомление",
|
||||
message: "Это тестовое уведомление",
|
||||
onDeny: () => {},
|
||||
onAllow: () => {},
|
||||
});
|
||||
}, 1000);
|
||||
return () => clearTimeout(timer);
|
||||
}, []);
|
||||
// useEffect(() => {
|
||||
// addToast({
|
||||
// id: crypto.randomUUID(),
|
||||
// type: "warning",
|
||||
// title: "Тестовое предупреждение",
|
||||
// message: "Это тестовое предупреждение",
|
||||
// onDeny: () => {},
|
||||
// onAllow: () => {},
|
||||
// });
|
||||
// const timer = setTimeout(() => {
|
||||
// addToast({
|
||||
// id: crypto.randomUUID(),
|
||||
// type: "notification",
|
||||
// title: "Тестовое уведомление",
|
||||
// message: "Это тестовое уведомление",
|
||||
// onDeny: () => {},
|
||||
// onAllow: () => {},
|
||||
// });
|
||||
// }, 1000);
|
||||
// return () => clearTimeout(timer);
|
||||
// }, []);
|
||||
|
||||
return (
|
||||
<div className="py-8 min-h-screen bg-gray-50">
|
||||
@@ -62,9 +63,7 @@ function HomePage() {
|
||||
|
||||
<FloatingActionButton
|
||||
variant="default"
|
||||
onClick={() =>
|
||||
setPopup(<SharePopup link="https://estate.stream/ahdy12jdco1" />)
|
||||
}
|
||||
onClick={() => setPopup(<ParticipantsPopup />)}
|
||||
>
|
||||
<div className="2xl:size-[1.111vw] size-4 text-white">
|
||||
<ShareFilledIcon />
|
||||
|
||||
Reference in New Issue
Block a user