Merge branch 'main' of http://192.168.1.163:3000/inmake/stream.graff.tech-new
This commit is contained in:
@@ -0,0 +1,34 @@
|
||||
import { AnimatePresence, motion } from "motion/react";
|
||||
import clsx from "clsx";
|
||||
|
||||
interface ActionsSidebarWrapperProps {
|
||||
children: React.ReactNode;
|
||||
className?: string;
|
||||
show?: boolean;
|
||||
}
|
||||
|
||||
function ActionsSidebarWrapper({
|
||||
children,
|
||||
className,
|
||||
show = true,
|
||||
}: ActionsSidebarWrapperProps) {
|
||||
return (
|
||||
<AnimatePresence>
|
||||
{show && (
|
||||
<motion.div
|
||||
initial={{ opacity: 0 }}
|
||||
animate={{ opacity: 1 }}
|
||||
exit={{ opacity: 0 }}
|
||||
className={clsx(
|
||||
"flex 2xl:flex 2xl:gap-[0.556vw] 2xl:flex-col gap-2 max-2xl:p-2 max-2xl:rounded-[32px] absolute 2xl:top-1/2 2xl:-translate-y-1/2 2xl:right-[1.111vw] max-2xl:left-1/2 max-2xl:-translate-x-1/2 max-2xl:bottom-2 max-2xl:landscape:bg-[#00000026] max-2xl:landscape:backdrop-blur",
|
||||
className
|
||||
)}
|
||||
>
|
||||
{children}
|
||||
</motion.div>
|
||||
)}
|
||||
</AnimatePresence>
|
||||
);
|
||||
}
|
||||
|
||||
export default ActionsSidebarWrapper;
|
||||
@@ -1,7 +1,7 @@
|
||||
import { useState, useRef, useEffect } from "react";
|
||||
import Button from "./Button";
|
||||
import MoreIcon from "../icons/MoreIcon";
|
||||
import { AnimatePresence, motion } from "motion/react";
|
||||
import Popover from "./Popover";
|
||||
|
||||
interface ActionsPopoverProps {
|
||||
options: {
|
||||
@@ -14,10 +14,9 @@ interface ActionsPopoverProps {
|
||||
|
||||
export default function ActionsPopover({ options }: ActionsPopoverProps) {
|
||||
const [isOpened, setIsOpened] = useState(false);
|
||||
const [openUpwards, setOpenUpwards] = useState(false);
|
||||
|
||||
const popoverRef = useRef<HTMLDivElement>(null);
|
||||
const buttonRef = useRef<HTMLButtonElement>(null);
|
||||
const [menuHeight, setMenuHeight] = useState(0);
|
||||
|
||||
useEffect(() => {
|
||||
const handleClickOutside = (event: MouseEvent | TouchEvent) => {
|
||||
@@ -37,15 +36,6 @@ export default function ActionsPopover({ options }: ActionsPopoverProps) {
|
||||
};
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
if (isOpened && buttonRef.current) {
|
||||
const buttonRect = buttonRef.current.getBoundingClientRect();
|
||||
const spaceBelow = window.innerHeight - buttonRect.bottom;
|
||||
const spaceAbove = buttonRect.top;
|
||||
setOpenUpwards(spaceBelow < menuHeight && spaceAbove > menuHeight);
|
||||
}
|
||||
}, [isOpened, menuHeight, options.length]);
|
||||
|
||||
return (
|
||||
<div className="relative" ref={popoverRef}>
|
||||
<button
|
||||
@@ -57,35 +47,24 @@ export default function ActionsPopover({ options }: ActionsPopoverProps) {
|
||||
<MoreIcon />
|
||||
</div>
|
||||
</button>
|
||||
|
||||
<AnimatePresence>
|
||||
{isOpened && (
|
||||
<motion.div
|
||||
initial={{ opacity: 0 }}
|
||||
animate={{ opacity: 1 }}
|
||||
exit={{ opacity: 0 }}
|
||||
ref={(el) => {
|
||||
setMenuHeight(el?.offsetHeight || 0);
|
||||
}}
|
||||
className={`absolute z-10 right-0 w-[13.333vw] bg-white rounded-[1.111vw] shadow-[0_4px_40px_0_rgba(15,16,17,0.1)] overflow-hidden ${
|
||||
openUpwards ? "bottom-[100%]" : "top-[100%]"
|
||||
}`}
|
||||
<Popover
|
||||
isOpened={isOpened}
|
||||
buttonRef={buttonRef}
|
||||
className="w-[17.222vw]"
|
||||
>
|
||||
{options.map((option) => (
|
||||
<Button
|
||||
variant="tertiary"
|
||||
className="p-[0.833vw] button-s w-full flex items-center !rounded-none !justify-start gap-[0.556vw]"
|
||||
key={option.label}
|
||||
onClick={option.onClick}
|
||||
disabled={option.disabled}
|
||||
>
|
||||
{options.map((option) => (
|
||||
<Button
|
||||
variant="tertiary"
|
||||
className="p-[0.833vw] button-s w-full flex items-center !rounded-none !justify-start gap-[0.556vw]"
|
||||
key={option.label}
|
||||
onClick={option.onClick}
|
||||
disabled={option.disabled}
|
||||
>
|
||||
<div className="size-[1.111vw] ">{option.icon}</div>
|
||||
{option.label}
|
||||
</Button>
|
||||
))}
|
||||
</motion.div>
|
||||
)}
|
||||
</AnimatePresence>
|
||||
<div className="size-[1.111vw] ">{option.icon}</div>
|
||||
{option.label}
|
||||
</Button>
|
||||
))}
|
||||
</Popover>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,78 @@
|
||||
import FloatingActionButton from "./FloatingActionButton";
|
||||
import Popover from "./Popover";
|
||||
import MoreIcon from "../icons/MoreIcon";
|
||||
import { useEffect, useRef, useState } from "react";
|
||||
import Button from "./Button";
|
||||
import ChatFilledIcon from "../icons/ChatFilledIcon";
|
||||
import UsersFilledIcon from "../icons/UsersFilledIcon";
|
||||
import ShareFilledIcon from "../icons/ShareFilledIcon";
|
||||
import CogFilledIcon from "../icons/CogFilledIcon";
|
||||
|
||||
function ControlsPopover() {
|
||||
const [isOpened, setIsOpened] = useState(false);
|
||||
const buttonRef = useRef<HTMLButtonElement>(null);
|
||||
|
||||
useEffect(() => {
|
||||
const handleClickOutside = (event: MouseEvent | TouchEvent) => {
|
||||
if (
|
||||
buttonRef.current &&
|
||||
!buttonRef.current.contains(event.target as Node)
|
||||
) {
|
||||
setIsOpened(false);
|
||||
}
|
||||
};
|
||||
|
||||
document.addEventListener("mousedown", handleClickOutside);
|
||||
document.addEventListener("touchstart", handleClickOutside);
|
||||
return () => {
|
||||
document.removeEventListener("mousedown", handleClickOutside);
|
||||
document.removeEventListener("touchstart", handleClickOutside);
|
||||
};
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<div className="2xl:hidden order-3 relative">
|
||||
<FloatingActionButton
|
||||
ref={buttonRef}
|
||||
className="!bg-[#7B60F3]"
|
||||
onClick={() => setIsOpened(!isOpened)}
|
||||
>
|
||||
<div className="size-4 text-white">
|
||||
<MoreIcon />
|
||||
</div>
|
||||
</FloatingActionButton>
|
||||
<Popover
|
||||
isOpened={isOpened}
|
||||
buttonRef={buttonRef}
|
||||
className="w-[248px] bottom-[72px]"
|
||||
>
|
||||
<Button variant="tertiary" className="w-full !justify-start">
|
||||
<div className="size-4">
|
||||
<ChatFilledIcon />
|
||||
</div>
|
||||
Чат
|
||||
</Button>
|
||||
<Button variant="tertiary" className="w-full !justify-start">
|
||||
<div className="size-4">
|
||||
<UsersFilledIcon />
|
||||
</div>
|
||||
Участники
|
||||
</Button>
|
||||
<Button variant="tertiary" className="w-full !justify-start">
|
||||
<div className="size-4">
|
||||
<ShareFilledIcon />
|
||||
</div>
|
||||
Пригласить
|
||||
</Button>
|
||||
<Button variant="tertiary" className="w-full !justify-start">
|
||||
<div className="size-4">
|
||||
<CogFilledIcon />
|
||||
</div>
|
||||
Настройки
|
||||
</Button>
|
||||
</Popover>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default ControlsPopover;
|
||||
@@ -3,7 +3,8 @@ import clsx from "clsx";
|
||||
interface FloatingActionButtonProps
|
||||
extends React.ButtonHTMLAttributes<HTMLButtonElement> {
|
||||
children: React.ReactNode;
|
||||
variant: "default" | "critical";
|
||||
variant?: "default" | "critical";
|
||||
ref?: React.RefObject<HTMLButtonElement | null>;
|
||||
}
|
||||
|
||||
function FloatingActionButton({
|
||||
@@ -11,11 +12,13 @@ function FloatingActionButton({
|
||||
variant = "default",
|
||||
className,
|
||||
onClick,
|
||||
ref,
|
||||
...props
|
||||
}: FloatingActionButtonProps) {
|
||||
return (
|
||||
<button
|
||||
onClick={onClick}
|
||||
ref={ref}
|
||||
className={clsx(
|
||||
"2xl:p-[0.833vw] p-3 rounded-full transition-all cursor-pointer disabled:!cursor-default outline-none backdrop-blur-[10px]",
|
||||
variant === "default" &&
|
||||
|
||||
@@ -0,0 +1,48 @@
|
||||
import { useEffect, useState } from "react";
|
||||
import { AnimatePresence, motion } from "motion/react";
|
||||
import clsx from "clsx";
|
||||
|
||||
interface PopoverProps {
|
||||
isOpened: boolean;
|
||||
buttonRef: React.RefObject<HTMLButtonElement | null>;
|
||||
children: React.ReactNode;
|
||||
className?: string;
|
||||
}
|
||||
|
||||
function Popover({ isOpened, buttonRef, children, className }: PopoverProps) {
|
||||
const [openUpwards, setOpenUpwards] = useState(false);
|
||||
const [menuHeight, setMenuHeight] = useState(0);
|
||||
|
||||
useEffect(() => {
|
||||
if (isOpened && buttonRef.current) {
|
||||
const buttonRect = buttonRef.current.getBoundingClientRect();
|
||||
const spaceBelow = window.innerHeight - buttonRect.bottom;
|
||||
const spaceAbove = buttonRect.top;
|
||||
setOpenUpwards(spaceBelow < menuHeight && spaceAbove > menuHeight);
|
||||
}
|
||||
}, [buttonRef, isOpened, menuHeight]);
|
||||
|
||||
return (
|
||||
<AnimatePresence>
|
||||
{isOpened && (
|
||||
<motion.div
|
||||
initial={{ opacity: 0 }}
|
||||
animate={{ opacity: 1 }}
|
||||
exit={{ opacity: 0 }}
|
||||
ref={(el) => {
|
||||
setMenuHeight(el?.offsetHeight || 0);
|
||||
}}
|
||||
className={clsx(
|
||||
"absolute z-10 right-0 bg-white 2xl:rounded-[1.111vw] shadow-[0_4px_40px_0_rgba(15,16,17,0.1)] overflow-hidden rounded-2xl 2xl:p-[0.278vw] p-1",
|
||||
openUpwards ? "bottom-full" : "top-full",
|
||||
className
|
||||
)}
|
||||
>
|
||||
{children}
|
||||
</motion.div>
|
||||
)}
|
||||
</AnimatePresence>
|
||||
);
|
||||
}
|
||||
|
||||
export default Popover;
|
||||
+3
-2
@@ -5,13 +5,14 @@ import { createBrowserRouter, RouterProvider } from "react-router";
|
||||
import SessionPage from "./pages/SessionPage";
|
||||
import LoginPage from "./pages/LoginPage";
|
||||
import RegisterPage from "./pages/RegisterPage";
|
||||
import TestPage from "./pages/TestPage";
|
||||
// import TestPage from "./pages/TestPage";
|
||||
import { QueryClientProvider } from "@tanstack/react-query";
|
||||
import { queryClient } from "./lib/queryClient";
|
||||
import ProtectedRoute from "./components/ProtectedRoute";
|
||||
import PublicRoute from "./components/PublicRoute";
|
||||
import ModalContainer from "./components/ModalContainer";
|
||||
import PopupContainer from "./components/PopupContainer";
|
||||
import NewSessionPage from "./pages/NewSessionPage";
|
||||
|
||||
const router = createBrowserRouter([
|
||||
{
|
||||
@@ -40,7 +41,7 @@ const router = createBrowserRouter([
|
||||
},
|
||||
{
|
||||
path: "/test",
|
||||
element: <TestPage />,
|
||||
element: <NewSessionPage />,
|
||||
},
|
||||
{
|
||||
path: "/sessions/:id",
|
||||
|
||||
@@ -0,0 +1,70 @@
|
||||
import ActionsSidebarWrapper from "../components/ActionsSidebarWrapper";
|
||||
import ChatFilledIcon from "../components/icons/ChatFilledIcon";
|
||||
import CogFilledIcon from "../components/icons/CogFilledIcon";
|
||||
import ExitFilledIcon from "../components/icons/ExitFilledIcon";
|
||||
import FullscreenIcon from "../components/icons/FullscreenIcon";
|
||||
import MicrophoneFilledIcon from "../components/icons/MicrophoneFilledIcon";
|
||||
import ShareFilledIcon from "../components/icons/ShareFilledIcon";
|
||||
import UsersFilledIcon from "../components/icons/UsersFilledIcon";
|
||||
import VideoOffFilledIcon from "../components/icons/VideoOffFilledIcon";
|
||||
import FloatingActionButton from "../components/ui/FloatingActionButton";
|
||||
import ParticipantsPopup from "../components/popups/ParticipantsPopup";
|
||||
import usePopupStore from "../store/popupStore";
|
||||
import ControlsPopover from "../components/ui/ControlsPopover";
|
||||
|
||||
function NewSessionPage() {
|
||||
const { setPopup } = usePopupStore();
|
||||
|
||||
return (
|
||||
<div className="relative w-screen h-screen bg-[#DADADA] order-3">
|
||||
<ActionsSidebarWrapper>
|
||||
<FloatingActionButton
|
||||
className="max-2xl:hidden"
|
||||
onClick={() => setPopup(<ParticipantsPopup />)}
|
||||
>
|
||||
<div className="2xl:size-[1.111vw] text-white">
|
||||
<ChatFilledIcon />
|
||||
</div>
|
||||
</FloatingActionButton>
|
||||
<FloatingActionButton className="max-2xl:hidden">
|
||||
<div className="2xl:size-[1.111vw] text-white">
|
||||
<UsersFilledIcon />
|
||||
</div>
|
||||
</FloatingActionButton>
|
||||
<FloatingActionButton className="max-2xl:hidden">
|
||||
<div className="2xl:size-[1.111vw] text-white">
|
||||
<ShareFilledIcon />
|
||||
</div>
|
||||
</FloatingActionButton>
|
||||
<FloatingActionButton className="max-2xl:hidden">
|
||||
<div className="2xl:size-[1.111vw] text-white">
|
||||
<CogFilledIcon />
|
||||
</div>
|
||||
</FloatingActionButton>
|
||||
<FloatingActionButton className="2xl:hidden">
|
||||
<div className="size-4 text-white">
|
||||
<MicrophoneFilledIcon />
|
||||
</div>
|
||||
</FloatingActionButton>
|
||||
<FloatingActionButton className="2xl:hidden">
|
||||
<div className="size-4 text-white">
|
||||
<VideoOffFilledIcon />
|
||||
</div>
|
||||
</FloatingActionButton>
|
||||
<FloatingActionButton className="max-2xl:order-2">
|
||||
<div className="2xl:size-[1.111vw] size-4 text-white">
|
||||
<FullscreenIcon />
|
||||
</div>
|
||||
</FloatingActionButton>
|
||||
<FloatingActionButton variant="critical" className="max-2xl:order-1">
|
||||
<div className="2xl:size-[1.111vw] size-4 text-white">
|
||||
<ExitFilledIcon />
|
||||
</div>
|
||||
</FloatingActionButton>
|
||||
<ControlsPopover />
|
||||
</ActionsSidebarWrapper>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default NewSessionPage;
|
||||
@@ -4,7 +4,7 @@ export default {
|
||||
theme: {
|
||||
extend: {},
|
||||
screens: {
|
||||
"2xl": { min: "1440px" },
|
||||
"2xl": "1440px",
|
||||
},
|
||||
},
|
||||
plugins: [],
|
||||
|
||||
Reference in New Issue
Block a user