From d4d5bf609fb1e6022af1c2162de301d47d3a50fe Mon Sep 17 00:00:00 2001 From: Lanskikh Date: Wed, 15 Oct 2025 17:00:30 +0500 Subject: [PATCH] Refactor UI components and add NewSessionPage; replace TestPage with NewSessionPage, implement ActionsSidebarWrapper, and enhance ActionsPopover and ControlsPopover with Popover component for improved UI interactions. --- .../src/components/ActionsSidebarWrapper.tsx | 34 ++++++++ client/src/components/ui/ActionsPopover.tsx | 59 +++++--------- client/src/components/ui/ControlsPopover.tsx | 78 +++++++++++++++++++ .../components/ui/FloatingActionButton.tsx | 5 +- client/src/components/ui/Popover.tsx | 48 ++++++++++++ client/src/main.tsx | 5 +- client/src/pages/NewSessionPage.tsx | 70 +++++++++++++++++ client/tailwind.config.js | 2 +- 8 files changed, 257 insertions(+), 44 deletions(-) create mode 100644 client/src/components/ActionsSidebarWrapper.tsx create mode 100644 client/src/components/ui/ControlsPopover.tsx create mode 100644 client/src/components/ui/Popover.tsx create mode 100644 client/src/pages/NewSessionPage.tsx diff --git a/client/src/components/ActionsSidebarWrapper.tsx b/client/src/components/ActionsSidebarWrapper.tsx new file mode 100644 index 0000000..eed1306 --- /dev/null +++ b/client/src/components/ActionsSidebarWrapper.tsx @@ -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 ( + + {show && ( + + {children} + + )} + + ); +} + +export default ActionsSidebarWrapper; diff --git a/client/src/components/ui/ActionsPopover.tsx b/client/src/components/ui/ActionsPopover.tsx index 2fedfe7..203f8dd 100644 --- a/client/src/components/ui/ActionsPopover.tsx +++ b/client/src/components/ui/ActionsPopover.tsx @@ -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(null); const buttonRef = useRef(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 (
- - - {isOpened && ( - { - 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%]" - }`} + + {options.map((option) => ( + - ))} - - )} - +
{option.icon}
+ {option.label} + + ))} + ); } diff --git a/client/src/components/ui/ControlsPopover.tsx b/client/src/components/ui/ControlsPopover.tsx new file mode 100644 index 0000000..a7b3e1d --- /dev/null +++ b/client/src/components/ui/ControlsPopover.tsx @@ -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(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 ( +
+ setIsOpened(!isOpened)} + > +
+ +
+
+ + + + + + +
+ ); +} + +export default ControlsPopover; diff --git a/client/src/components/ui/FloatingActionButton.tsx b/client/src/components/ui/FloatingActionButton.tsx index 75f9641..41f1d15 100644 --- a/client/src/components/ui/FloatingActionButton.tsx +++ b/client/src/components/ui/FloatingActionButton.tsx @@ -3,7 +3,8 @@ import clsx from "clsx"; interface FloatingActionButtonProps extends React.ButtonHTMLAttributes { children: React.ReactNode; - variant: "default" | "critical"; + variant?: "default" | "critical"; + ref?: React.RefObject; } function FloatingActionButton({ @@ -11,11 +12,13 @@ function FloatingActionButton({ variant = "default", className, onClick, + ref, ...props }: FloatingActionButtonProps) { return (