diff --git a/frontend/src/Hero.tsx b/frontend/src/Hero.tsx index a000ab2e4..33710b846 100644 --- a/frontend/src/Hero.tsx +++ b/frontend/src/Hero.tsx @@ -37,12 +37,14 @@ export default function Hero({ diff --git a/frontend/src/assets/user.png b/frontend/src/assets/user.png index 3cc5d2f11..259d6b0bc 100644 Binary files a/frontend/src/assets/user.png and b/frontend/src/assets/user.png differ diff --git a/frontend/src/components/ContextMenu.tsx b/frontend/src/components/ContextMenu.tsx new file mode 100644 index 000000000..9a3046e4a --- /dev/null +++ b/frontend/src/components/ContextMenu.tsx @@ -0,0 +1,122 @@ +import { SyntheticEvent, useRef, useEffect } from 'react'; + +export interface MenuOption { + icon?: string; + label: string; + onClick: (event: SyntheticEvent) => void; + variant?: 'primary' | 'danger'; + iconClassName?: string; + iconWidth?: number; + iconHeight?: number; +} + +interface ContextMenuProps { + isOpen: boolean; + setIsOpen: (isOpen: boolean) => void; + options: MenuOption[]; + anchorRef: React.RefObject; + className?: string; + position?: 'bottom-right' | 'bottom-left' | 'top-right' | 'top-left'; + offset?: { x: number; y: number }; +} + +export default function ContextMenu({ + isOpen, + setIsOpen, + options, + anchorRef, + className = '', + position = 'bottom-right', + offset = { x: 1, y: 5 }, +}: ContextMenuProps) { + const menuRef = useRef(null); + + const handleClickOutside = (event: MouseEvent) => { + if (menuRef.current && !menuRef.current.contains(event.target as Node)) { + setIsOpen(false); + } + }; + + useEffect(() => { + document.addEventListener('mousedown', handleClickOutside); + return () => { + document.removeEventListener('mousedown', handleClickOutside); + }; + }, []); + + const getPositionClasses = () => { + const positionMap = { + 'bottom-right': 'translate-x-1 translate-y-5', + 'bottom-left': '-translate-x-full translate-y-5', + 'top-right': 'translate-x-1 -translate-y-full', + 'top-left': '-translate-x-full -translate-y-full', + }; + return positionMap[position]; + }; + + if (!isOpen) return null; + + const getMenuPosition = () => { + if (!anchorRef.current) return {}; + + const rect = anchorRef.current.getBoundingClientRect(); + return { + top: `${rect.top + window.scrollY + offset.y}px`, + }; + }; + + const getOptionStyles = (option: MenuOption, index: number) => { + if (option.variant === 'danger') { + return ` + dark:text-red-2000 dark:hover:bg-charcoal-grey + text-rosso-corsa hover:bg-bright-gray + }`; + } + + return ` + dark:text-bright-gray dark:hover:bg-charcoal-grey + text-eerie-black hover:bg-bright-gray + }`; + }; + + return ( +
+
+ {options.map((option, index) => ( + + ))} +
+
+ ); +} diff --git a/frontend/src/components/Input.tsx b/frontend/src/components/Input.tsx index e703ff147..247446ef6 100644 --- a/frontend/src/components/Input.tsx +++ b/frontend/src/components/Input.tsx @@ -1,4 +1,5 @@ import { InputProps } from './types'; +import { useRef } from 'react'; const Input = ({ id, @@ -27,15 +28,19 @@ const Input = ({ thin: 'border', thick: 'border-2', }; + + const inputRef = useRef(null); + return (
{children} - {label && ( -
- - {label} - {required && ( - - * - - )} - -
+ {(label || placeholder) && ( + )}
); diff --git a/frontend/src/components/ToggleSwitch.tsx b/frontend/src/components/ToggleSwitch.tsx index 061d25632..8293ece28 100644 --- a/frontend/src/components/ToggleSwitch.tsx +++ b/frontend/src/components/ToggleSwitch.tsx @@ -6,9 +6,8 @@ type ToggleSwitchProps = { className?: string; label?: string; disabled?: boolean; - activeColor?: string; - inactiveColor?: string; - id?: string; + size?: 'small' | 'medium' | 'large'; + labelPosition?: 'left' | 'right'; }; const ToggleSwitch: React.FC = ({ @@ -17,17 +16,44 @@ const ToggleSwitch: React.FC = ({ className = '', label, disabled = false, - activeColor = 'bg-purple-30', - inactiveColor = 'bg-transparent', - id, + size = 'medium', + labelPosition = 'left', }) => { + // Size configurations + const sizeConfig = { + small: { + box: 'h-6 w-10', + toggle: 'h-4 w-4 left-1 top-1', + translate: 'translate-x-4', + }, + medium: { + box: 'h-8 w-14', + toggle: 'h-6 w-6 left-1 top-1', + translate: 'translate-x-full', + }, + large: { + box: 'h-10 w-16', + toggle: 'h-8 w-8 left-1 top-1', + translate: 'translate-x-full', + }, + }; + + const { box, toggle, translate } = sizeConfig[size]; + return (