diff options
| author | Wolfboy248 <georgejvindkarlsen@gmail.com> | 2025-08-25 09:36:51 +0200 |
|---|---|---|
| committer | Wolfboy248 <georgejvindkarlsen@gmail.com> | 2025-08-25 09:36:51 +0200 |
| commit | 246eabe4a46d2585d653738e46089ed2bfada8bd (patch) | |
| tree | c86dd05119b0aa086a5d5401dd6a48d90a6f3bbd /frontend/src | |
| parent | Reorganised Maplist and Sidebar (diff) | |
| download | lphub-246eabe4a46d2585d653738e46089ed2bfada8bd.tar.gz lphub-246eabe4a46d2585d653738e46089ed2bfada8bd.tar.bz2 lphub-246eabe4a46d2585d653738e46089ed2bfada8bd.zip | |
Restructured sidebar and implemented links var
Diffstat (limited to 'frontend/src')
| -rw-r--r-- | frontend/src/App.css | 1 | ||||
| -rw-r--r-- | frontend/src/App.tsx | 4 | ||||
| -rw-r--r-- | frontend/src/components/Sidebar/Content.tsx | 117 | ||||
| -rw-r--r-- | frontend/src/components/Sidebar/Footer.tsx | 56 | ||||
| -rw-r--r-- | frontend/src/components/Sidebar/Header.tsx | 6 | ||||
| -rw-r--r-- | frontend/src/components/Sidebar/Links.ts | 53 | ||||
| -rw-r--r-- | frontend/src/components/Sidebar/Search.tsx | 66 | ||||
| -rw-r--r-- | frontend/src/components/Sidebar/Sidebar.module.css | 16 | ||||
| -rw-r--r-- | frontend/src/components/Sidebar/Sidebar.tsx | 82 |
9 files changed, 214 insertions, 187 deletions
diff --git a/frontend/src/App.css b/frontend/src/App.css index 3e0d813..d443222 100644 --- a/frontend/src/App.css +++ b/frontend/src/App.css | |||
| @@ -4,6 +4,7 @@ | |||
| 4 | 4 | ||
| 5 | @theme { | 5 | @theme { |
| 6 | --color-main: #141520; | 6 | --color-main: #141520; |
| 7 | --color-input: #161723; | ||
| 7 | --color-panel: #202232; | 8 | --color-panel: #202232; |
| 8 | --color-block: #2b2e46; | 9 | --color-block: #2b2e46; |
| 9 | --color-bright: #333753; | 10 | --color-bright: #333753; |
diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index 5d0c8eb..2451307 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx | |||
| @@ -89,7 +89,7 @@ const App: React.FC = () => { | |||
| 89 | games={games} | 89 | games={games} |
| 90 | /> | 90 | /> |
| 91 | 91 | ||
| 92 | <div className="flex flex-row not-md:flex-col h-[100vh]"> | 92 | <div className="flex flex-row not-md:flex-col h-screen"> |
| 93 | 93 | ||
| 94 | <Sidebar | 94 | <Sidebar |
| 95 | setToken={setToken} | 95 | setToken={setToken} |
| @@ -98,7 +98,7 @@ const App: React.FC = () => { | |||
| 98 | onUploadRun={() => setUploadRunDialog(true)} | 98 | onUploadRun={() => setUploadRunDialog(true)} |
| 99 | /> | 99 | /> |
| 100 | 100 | ||
| 101 | <main className="w-full"> | 101 | <main className="w-full h-screen"> |
| 102 | 102 | ||
| 103 | <Routes> | 103 | <Routes> |
| 104 | <Route path="/" element={<Homepage />} /> | 104 | <Route path="/" element={<Homepage />} /> |
diff --git a/frontend/src/components/Sidebar/Content.tsx b/frontend/src/components/Sidebar/Content.tsx index 4051b08..3d9533a 100644 --- a/frontend/src/components/Sidebar/Content.tsx +++ b/frontend/src/components/Sidebar/Content.tsx | |||
| @@ -1,124 +1,45 @@ | |||
| 1 | import React, { useRef } from "react"; | 1 | import React, { useRef } from "react"; |
| 2 | import { Link } from "react-router-dom"; | 2 | import { Link, useLocation } from "react-router-dom"; |
| 3 | import { UserProfile } from "@customTypes/Profile"; | 3 | import { UserProfile } from "@customTypes/Profile"; |
| 4 | import { Search } from "@customTypes/Search"; | ||
| 5 | import { API } from "@api/Api"; | ||
| 6 | 4 | ||
| 7 | import styles from "./Sidebar.module.css"; | 5 | import styles from "./Sidebar.module.css"; |
| 8 | 6 | ||
| 9 | import { | 7 | import { |
| 10 | FlagIcon, | ||
| 11 | HomeIcon, | ||
| 12 | PortalIcon, | ||
| 13 | SearchIcon, | 8 | SearchIcon, |
| 14 | } from "../../images/Images"; | 9 | } from "../../images/Images"; |
| 15 | 10 | ||
| 11 | import links from "./Links"; | ||
| 12 | |||
| 16 | interface ContentProps { | 13 | interface ContentProps { |
| 17 | profile?: UserProfile; | 14 | profile?: UserProfile; |
| 15 | isSearching: boolean; | ||
| 16 | selectedButtonIndex: number | ||
| 18 | isSidebarOpen: boolean; | 17 | isSidebarOpen: boolean; |
| 19 | sidebarButtonRefs: React.RefObject<(HTMLButtonElement | null)[]>; | ||
| 20 | getButtonClasses: (buttonIndex: number) => string; | ||
| 21 | handle_sidebar_click: (clicked_sidebar_idx: number) => void; | 18 | handle_sidebar_click: (clicked_sidebar_idx: number) => void; |
| 22 | }; | 19 | }; |
| 23 | 20 | ||
| 24 | const Content: React.FC<ContentProps> = ({ profile, isSidebarOpen, sidebarButtonRefs, getButtonClasses, handle_sidebar_click }) => { | 21 | const _Content: React.FC<ContentProps> = ({ profile, isSearching, selectedButtonIndex, isSidebarOpen, handle_sidebar_click }) => { |
| 25 | const [searchData, setSearchData] = React.useState<Search | undefined>( | ||
| 26 | undefined | ||
| 27 | ); | ||
| 28 | |||
| 29 | const searchbarRef = useRef<HTMLInputElement>(null); | ||
| 30 | |||
| 31 | const _handle_search_change = async (q: string) => { | ||
| 32 | const searchResponse = await API.get_search(q); | ||
| 33 | setSearchData(searchResponse); | ||
| 34 | }; | ||
| 35 | |||
| 36 | const iconClasses = ""; | ||
| 37 | 22 | ||
| 38 | return ( | 23 | return ( |
| 39 | <div className="h-full"> | 24 | <div className="h-full"> |
| 40 | 25 | ||
| 41 | <div className="px-2"> | 26 | <div className="px-2 my-2.5"> |
| 42 | <div className={`${styles.button}`}> | 27 | <button onClick={() => handle_sidebar_click(0)} className={`${styles.button} ${selectedButtonIndex == 0 ? styles["button-selected"] : ""} ${isSearching ? styles["button-hidden"] : ""}`}> |
| 43 | <img src={SearchIcon} alt="Search" className={iconClasses} /> | 28 | <img src={SearchIcon} alt="Search" /> |
| 44 | <span className="text-white font-[--font-barlow-semicondensed-regular] truncate">Search</span> | 29 | <span>Search</span> |
| 45 | </div> | 30 | </button> |
| 46 | |||
| 47 | <div className="min-w-0"> | ||
| 48 | <input | ||
| 49 | ref={searchbarRef} | ||
| 50 | type="text" | ||
| 51 | id="searchbar" | ||
| 52 | placeholder="Search for map or a player..." | ||
| 53 | onChange={e => _handle_search_change(e.target.value)} | ||
| 54 | className="w-full p-2 bg-input text-foreground border border-border rounded-lg text-sm min-w-0" | ||
| 55 | /> | ||
| 56 | |||
| 57 | {searchData && ( | ||
| 58 | <div className="mt-2 max-h-40 overflow-y-auto min-w-0"> | ||
| 59 | {searchData?.maps.map((q, index) => ( | ||
| 60 | <Link to={`/maps/${q.id}`} className="block p-2 mb-1 bg-surface1 rounded hover:bg-surface2 transition-colors min-w-0" key={index}> | ||
| 61 | <span className="block text-xs text-subtext1 truncate">{q.game}</span> | ||
| 62 | <span className="block text-xs text-subtext1 truncate">{q.chapter}</span> | ||
| 63 | <span className="block text-sm text-foreground truncate">{q.map}</span> | ||
| 64 | </Link> | ||
| 65 | ))} | ||
| 66 | {searchData?.players.map((q, index) => ( | ||
| 67 | <Link | ||
| 68 | to={ | ||
| 69 | profile && q.steam_id === profile.steam_id | ||
| 70 | ? `/profile` | ||
| 71 | : `/users/${q.steam_id}` | ||
| 72 | } | ||
| 73 | className="flex items-center p-2 mb-1 bg-surface1 rounded hover:bg-surface2 transition-colors min-w-0" | ||
| 74 | key={index} | ||
| 75 | > | ||
| 76 | <img src={q.avatar_link} alt="pfp" className="w-6 h-6 rounded-full mr-2 flex-shrink-0" /> | ||
| 77 | <span className="text-sm text-foreground truncate"> | ||
| 78 | {q.user_name} | ||
| 79 | </span> | ||
| 80 | </Link> | ||
| 81 | ))} | ||
| 82 | </div> | ||
| 83 | )} | ||
| 84 | </div> | ||
| 85 | </div> | 31 | </div> |
| 86 | 32 | ||
| 87 | <div className="flex-1 min-w-0"> | 33 | <div className="flex-1 min-w-0 mt-12"> |
| 88 | <nav className="px-2 flex flex-col gap-2"> | 34 | <nav className="px-2 flex flex-col gap-2"> |
| 89 | {[ | 35 | {links.content.map(({ to, icon, label }, i) => ( |
| 90 | { | 36 | <Link to={to} tabIndex={-1} key={i + 1}> |
| 91 | to: "/", | ||
| 92 | refIndex: 1, | ||
| 93 | icon: HomeIcon, | ||
| 94 | alt: "Home", | ||
| 95 | label: "Home Page", | ||
| 96 | }, | ||
| 97 | { | ||
| 98 | to: "/games", | ||
| 99 | refIndex: 2, | ||
| 100 | icon: PortalIcon, | ||
| 101 | alt: "Games", | ||
| 102 | label: "Games", | ||
| 103 | }, | ||
| 104 | { | ||
| 105 | to: "/rankings", | ||
| 106 | refIndex: 3, | ||
| 107 | icon: FlagIcon, | ||
| 108 | alt: "Rankings", | ||
| 109 | label: "Rankings", | ||
| 110 | }, | ||
| 111 | ].map(({ to, refIndex, icon, alt, label }) => ( | ||
| 112 | <Link to={to} tabIndex={-1} key={refIndex}> | ||
| 113 | <button | 37 | <button |
| 114 | ref={el => { | 38 | className={`${styles.button} ${selectedButtonIndex == i + 1 ? styles["button-selected"] : ""} ${isSearching ? styles["button-hidden"] : ""}`} |
| 115 | sidebarButtonRefs.current[refIndex] = el | 39 | onClick={() => handle_sidebar_click(i + 1)} |
| 116 | }} | ||
| 117 | className={`${styles.button}`} | ||
| 118 | onClick={() => handle_sidebar_click(refIndex)} | ||
| 119 | > | 40 | > |
| 120 | <img src={icon} alt={alt} className={iconClasses} /> | 41 | <img src={icon} /> |
| 121 | <span className=""> | 42 | <span> |
| 122 | {label} | 43 | {label} |
| 123 | </span> | 44 | </span> |
| 124 | </button> | 45 | </button> |
| @@ -130,4 +51,4 @@ const Content: React.FC<ContentProps> = ({ profile, isSidebarOpen, sidebarButton | |||
| 130 | ); | 51 | ); |
| 131 | } | 52 | } |
| 132 | 53 | ||
| 133 | export default Content; | 54 | export default _Content; |
diff --git a/frontend/src/components/Sidebar/Footer.tsx b/frontend/src/components/Sidebar/Footer.tsx index 070301a..8e910b3 100644 --- a/frontend/src/components/Sidebar/Footer.tsx +++ b/frontend/src/components/Sidebar/Footer.tsx | |||
| @@ -12,26 +12,28 @@ import { | |||
| 12 | HelpIcon, | 12 | HelpIcon, |
| 13 | } from "../../images/Images"; | 13 | } from "../../images/Images"; |
| 14 | 14 | ||
| 15 | import links from "./Links"; | ||
| 16 | |||
| 15 | interface FooterProps { | 17 | interface FooterProps { |
| 16 | profile?: UserProfile; | 18 | profile?: UserProfile; |
| 19 | isSearching: boolean; | ||
| 20 | selectedButtonIndex: number; | ||
| 17 | onUploadRun: () => void; | 21 | onUploadRun: () => void; |
| 18 | setProfile: React.Dispatch<React.SetStateAction<UserProfile | undefined>>; | 22 | setProfile: React.Dispatch<React.SetStateAction<UserProfile | undefined>>; |
| 19 | setToken: React.Dispatch<React.SetStateAction<string | undefined>>; | 23 | setToken: React.Dispatch<React.SetStateAction<string | undefined>>; |
| 20 | sidebarButtonRefs: React.RefObject<(HTMLButtonElement | null)[]>; | ||
| 21 | getButtonClasses: (buttonIndex: number) => string; | ||
| 22 | handle_sidebar_click: (clicked_sidebar_idx: number) => void; | 24 | handle_sidebar_click: (clicked_sidebar_idx: number) => void; |
| 23 | }; | 25 | }; |
| 24 | 26 | ||
| 25 | const Footer: React.FC<FooterProps> = ({ profile, onUploadRun, setToken, setProfile, sidebarButtonRefs, getButtonClasses, handle_sidebar_click }) => { | 27 | const _Footer: React.FC<FooterProps> = ({ profile, isSearching, selectedButtonIndex, onUploadRun, setToken, setProfile, handle_sidebar_click }) => { |
| 26 | const uploadRunRef = useRef<HTMLButtonElement>(null); | 28 | const uploadRunRef = useRef<HTMLButtonElement>(null); |
| 27 | 29 | ||
| 28 | return ( | 30 | return ( |
| 29 | <div className=""> | 31 | <div className="px-2 gap-2 flex flex-col mb-2"> |
| 30 | {profile && profile.profile && ( | 32 | {profile && profile.profile && ( |
| 31 | <button | 33 | <button |
| 32 | ref={uploadRunRef} | 34 | ref={uploadRunRef} |
| 33 | id="upload-run" | 35 | id="upload-run" |
| 34 | className={getButtonClasses(-1)} | 36 | className={``} |
| 35 | onClick={() => onUploadRun()} | 37 | onClick={() => onUploadRun()} |
| 36 | > | 38 | > |
| 37 | <img src={UploadIcon} alt="Upload" className={``} /> | 39 | <img src={UploadIcon} alt="Upload" className={``} /> |
| @@ -39,42 +41,30 @@ const Footer: React.FC<FooterProps> = ({ profile, onUploadRun, setToken, setProf | |||
| 39 | </button> | 41 | </button> |
| 40 | )} | 42 | )} |
| 41 | 43 | ||
| 42 | <div className={true ? 'min-w-0' : 'flex justify-center'}> | 44 | {/* <div className={true ? 'min-w-0' : 'flex justify-center'}> |
| 43 | <Login | 45 | <Login |
| 44 | setToken={setToken} | 46 | setToken={setToken} |
| 45 | profile={profile} | 47 | profile={profile} |
| 46 | setProfile={setProfile} | 48 | setProfile={setProfile} |
| 47 | isOpen={true} | 49 | isOpen={true} |
| 48 | /> | 50 | /> |
| 49 | </div> | 51 | </div> */} |
| 50 | |||
| 51 | <Link to="/rules" tabIndex={-1}> | ||
| 52 | <button | ||
| 53 | ref={el => { | ||
| 54 | sidebarButtonRefs.current[5] = el | ||
| 55 | }} | ||
| 56 | className={`${styles.button}`} | ||
| 57 | onClick={() => handle_sidebar_click(5)} | ||
| 58 | > | ||
| 59 | <img src={BookIcon} alt="Rules" /> | ||
| 60 | {true && <span className="font-[--font-barlow-semicondensed-regular] truncate">Leaderboard Rules</span>} | ||
| 61 | </button> | ||
| 62 | </Link> | ||
| 63 | 52 | ||
| 64 | <Link to="/about" tabIndex={-1}> | 53 | {links.footer.map(({ to, icon, label }, i) => ( |
| 65 | <button | 54 | <Link to={to} tabIndex={-1} key={i}> |
| 66 | ref={el => { | 55 | <button |
| 67 | sidebarButtonRefs.current[6] = el | 56 | className={`${styles.button} ${selectedButtonIndex == links.content.length + i + 1 ? styles["button-selected"] : ""} ${isSearching ? styles["button-hidden"] : ""}`} |
| 68 | }} | 57 | onClick={() => handle_sidebar_click(links.content.length + i + 1)} |
| 69 | className={`${styles.button}`} | 58 | > |
| 70 | onClick={() => handle_sidebar_click(6)} | 59 | <img src={icon} /> |
| 71 | > | 60 | <span className=""> |
| 72 | <img src={HelpIcon} alt="About" /> | 61 | {label} |
| 73 | {true && <span className="font-[--font-barlow-semicondensed-regular] truncate">About LPHUB</span>} | 62 | </span> |
| 74 | </button> | 63 | </button> |
| 75 | </Link> | 64 | </Link> |
| 65 | ))} | ||
| 76 | </div> | 66 | </div> |
| 77 | ); | 67 | ); |
| 78 | } | 68 | } |
| 79 | 69 | ||
| 80 | export default Footer; | 70 | export default _Footer; |
diff --git a/frontend/src/components/Sidebar/Header.tsx b/frontend/src/components/Sidebar/Header.tsx index e990060..c317d38 100644 --- a/frontend/src/components/Sidebar/Header.tsx +++ b/frontend/src/components/Sidebar/Header.tsx | |||
| @@ -5,9 +5,9 @@ import { | |||
| 5 | LogoIcon, | 5 | LogoIcon, |
| 6 | } from "../../images/Images"; | 6 | } from "../../images/Images"; |
| 7 | 7 | ||
| 8 | const Header: React.FC = () => { | 8 | const _Header: React.FC = () => { |
| 9 | return ( | 9 | return ( |
| 10 | <div className="flex justify-center px-4 py-3 bg-gradient-to-t from-block to-bright"> | 10 | <div className="flex justify-center px-4 py-3 bg-gradient-to-t from-block to-bright md:w-80"> |
| 11 | <Link to="/" tabIndex={-1} className="flex gap-4"> | 11 | <Link to="/" tabIndex={-1} className="flex gap-4"> |
| 12 | <img src={LogoIcon} alt="Logo" className="h-18 translate-y-0.5" /> | 12 | <img src={LogoIcon} alt="Logo" className="h-18 translate-y-0.5" /> |
| 13 | <div className="text-[#fff] flex flex-col justify-center not-md:hidden"> | 13 | <div className="text-[#fff] flex flex-col justify-center not-md:hidden"> |
| @@ -23,4 +23,4 @@ const Header: React.FC = () => { | |||
| 23 | ) | 23 | ) |
| 24 | } | 24 | } |
| 25 | 25 | ||
| 26 | export default Header; | 26 | export default _Header; |
diff --git a/frontend/src/components/Sidebar/Links.ts b/frontend/src/components/Sidebar/Links.ts new file mode 100644 index 0000000..dfb621d --- /dev/null +++ b/frontend/src/components/Sidebar/Links.ts | |||
| @@ -0,0 +1,53 @@ | |||
| 1 | import { | ||
| 2 | FlagIcon, | ||
| 3 | HomeIcon, | ||
| 4 | PortalIcon, | ||
| 5 | BookIcon, | ||
| 6 | HelpIcon, | ||
| 7 | } from "../../images/Images"; | ||
| 8 | |||
| 9 | export interface SidebarLink { | ||
| 10 | to: string; | ||
| 11 | icon: any; | ||
| 12 | label: string; | ||
| 13 | }; | ||
| 14 | |||
| 15 | export interface SidebarLinks { | ||
| 16 | content: SidebarLink[]; | ||
| 17 | footer: SidebarLink[]; | ||
| 18 | } | ||
| 19 | |||
| 20 | const links: SidebarLinks = { | ||
| 21 | content: [ | ||
| 22 | { | ||
| 23 | to: "/", | ||
| 24 | icon: HomeIcon, | ||
| 25 | label: "Home Page" | ||
| 26 | }, | ||
| 27 | { | ||
| 28 | to: "/games", | ||
| 29 | icon: PortalIcon, | ||
| 30 | label: "Games" | ||
| 31 | }, | ||
| 32 | { | ||
| 33 | to: "/rankings", | ||
| 34 | icon: FlagIcon, | ||
| 35 | label: "Rankings" | ||
| 36 | }, | ||
| 37 | ], | ||
| 38 | |||
| 39 | footer: [ | ||
| 40 | { | ||
| 41 | to: "/rules", | ||
| 42 | icon: BookIcon, | ||
| 43 | label: "Leaderboard Rules" | ||
| 44 | }, | ||
| 45 | { | ||
| 46 | to: "/about", | ||
| 47 | icon: HelpIcon, | ||
| 48 | label: "About" | ||
| 49 | }, | ||
| 50 | ] | ||
| 51 | } | ||
| 52 | |||
| 53 | export default links; | ||
diff --git a/frontend/src/components/Sidebar/Search.tsx b/frontend/src/components/Sidebar/Search.tsx new file mode 100644 index 0000000..0c6b868 --- /dev/null +++ b/frontend/src/components/Sidebar/Search.tsx | |||
| @@ -0,0 +1,66 @@ | |||
| 1 | import React, { useRef } from "react"; | ||
| 2 | import { Link } from "react-router-dom"; | ||
| 3 | |||
| 4 | import { Search } from "@customTypes/Search"; | ||
| 5 | import { API } from "@api/Api"; | ||
| 6 | import { UserProfile } from "@customTypes/Profile"; | ||
| 7 | |||
| 8 | interface SearchProps { | ||
| 9 | profile?: UserProfile; | ||
| 10 | isSearching: boolean; | ||
| 11 | }; | ||
| 12 | |||
| 13 | const _Search: React.FC<SearchProps> = ({ profile, isSearching }) => { | ||
| 14 | const [searchData, setSearchData] = React.useState<Search | undefined>( | ||
| 15 | undefined | ||
| 16 | ); | ||
| 17 | |||
| 18 | const searchbarRef = useRef<HTMLInputElement>(null); | ||
| 19 | |||
| 20 | const _handle_search_change = async (q: string) => { | ||
| 21 | const searchResponse = await API.get_search(q); | ||
| 22 | setSearchData(searchResponse); | ||
| 23 | }; | ||
| 24 | return ( | ||
| 25 | <div className="flex w-full flex-col justify-between p-3"> | ||
| 26 | <input | ||
| 27 | ref={searchbarRef} | ||
| 28 | type="text" | ||
| 29 | id="searchbar" | ||
| 30 | placeholder="Search for map or a player..." | ||
| 31 | onChange={e => _handle_search_change(e.target.value)} | ||
| 32 | className="w-full py-2 px-[19px] bg-input rounded-[2000px] outline-none placeholder-bright placeholder:text-[18px] placeholder:font-barlow-semicondensed-regular" | ||
| 33 | /> | ||
| 34 | |||
| 35 | {searchData && ( | ||
| 36 | <div className="overflow-y-auto"> | ||
| 37 | {searchData?.maps.map((q, index) => ( | ||
| 38 | <Link to={`/maps/${q.id}`} className="block p-2 mb-1 bg-surface1 rounded hover:bg-surface2 transition-colors min-w-0" key={index}> | ||
| 39 | <span className="block text-xs text-subtext1 truncate">{q.game}</span> | ||
| 40 | <span className="block text-xs text-subtext1 truncate">{q.chapter}</span> | ||
| 41 | <span className="block text-sm text-foreground truncate">{q.map}</span> | ||
| 42 | </Link> | ||
| 43 | ))} | ||
| 44 | {searchData?.players.map((q, index) => ( | ||
| 45 | <Link | ||
| 46 | to={ | ||
| 47 | profile && q.steam_id === profile.steam_id | ||
| 48 | ? `/profile` | ||
| 49 | : `/users/${q.steam_id}` | ||
| 50 | } | ||
| 51 | className="flex items-center p-2 mb-1 bg-surface1 rounded hover:bg-surface2 transition-colors min-w-0" | ||
| 52 | key={index} | ||
| 53 | > | ||
| 54 | <img src={q.avatar_link} alt="pfp" className="w-6 h-6 rounded-full mr-2 flex-shrink-0" /> | ||
| 55 | <span className="text-sm text-foreground truncate"> | ||
| 56 | {q.user_name} | ||
| 57 | </span> | ||
| 58 | </Link> | ||
| 59 | ))} | ||
| 60 | </div> | ||
| 61 | )} | ||
| 62 | </div> | ||
| 63 | ) | ||
| 64 | } | ||
| 65 | |||
| 66 | export default _Search; | ||
diff --git a/frontend/src/components/Sidebar/Sidebar.module.css b/frontend/src/components/Sidebar/Sidebar.module.css index 8079676..9baf415 100644 --- a/frontend/src/components/Sidebar/Sidebar.module.css +++ b/frontend/src/components/Sidebar/Sidebar.module.css | |||
| @@ -6,18 +6,30 @@ | |||
| 6 | border-radius: 2000px; | 6 | border-radius: 2000px; |
| 7 | transition: all 0.2s ease; | 7 | transition: all 0.2s ease; |
| 8 | padding: 4px 0px; | 8 | padding: 4px 0px; |
| 9 | height: 48px; | ||
| 9 | } | 10 | } |
| 10 | 11 | ||
| 11 | .button:hover { | 12 | .button:hover, |
| 13 | .button-selected, | ||
| 14 | .button-selected:hover { | ||
| 12 | background-color: var(--color-panel); | 15 | background-color: var(--color-panel); |
| 13 | } | 16 | } |
| 14 | 17 | ||
| 15 | .button>img { | 18 | .button>img { |
| 16 | padding: 0px 8px; | ||
| 17 | height: 28px; | 19 | height: 28px; |
| 20 | width: 28px; | ||
| 21 | transform: translateX(10px); | ||
| 18 | } | 22 | } |
| 19 | 23 | ||
| 20 | button>span { | 24 | button>span { |
| 21 | font-size: 22px; | 25 | font-size: 22px; |
| 22 | font-family: var(--font-barlow-semicondensed-regular); | 26 | font-family: var(--font-barlow-semicondensed-regular); |
| 27 | white-space: nowrap; | ||
| 28 | margin-left: 16px; | ||
| 29 | } | ||
| 30 | |||
| 31 | .button-hidden>span { | ||
| 32 | cursor: none; | ||
| 33 | pointer-events: none; | ||
| 34 | opacity: 0; | ||
| 23 | } \ No newline at end of file | 35 | } \ No newline at end of file |
diff --git a/frontend/src/components/Sidebar/Sidebar.tsx b/frontend/src/components/Sidebar/Sidebar.tsx index 2dafa2b..1d58d2e 100644 --- a/frontend/src/components/Sidebar/Sidebar.tsx +++ b/frontend/src/components/Sidebar/Sidebar.tsx | |||
| @@ -2,9 +2,11 @@ import React, { useCallback, useRef } from "react"; | |||
| 2 | import { Link, useLocation } from "react-router-dom"; | 2 | import { Link, useLocation } from "react-router-dom"; |
| 3 | import { UserProfile } from "@customTypes/Profile"; | 3 | import { UserProfile } from "@customTypes/Profile"; |
| 4 | 4 | ||
| 5 | import Header from "./Header"; | 5 | import _Header from "./Header"; |
| 6 | import Footer from "./Footer"; | 6 | import _Footer from "./Footer"; |
| 7 | import Content from "./Content"; | 7 | import _Content from "./Content"; |
| 8 | import _Search from "./Search"; | ||
| 9 | import links from "./Links"; | ||
| 8 | 10 | ||
| 9 | interface SidebarProps { | 11 | interface SidebarProps { |
| 10 | setToken: React.Dispatch<React.SetStateAction<string | undefined>>; | 12 | setToken: React.Dispatch<React.SetStateAction<string | undefined>>; |
| @@ -19,78 +21,60 @@ const Sidebar: React.FC<SidebarProps> = ({ | |||
| 19 | setProfile, | 21 | setProfile, |
| 20 | onUploadRun, | 22 | onUploadRun, |
| 21 | }) => { | 23 | }) => { |
| 22 | // const [isSidebarLocked, setIsSidebarLocked] = React.useState<boolean>(false); | 24 | const [isSearching, setIsSearching] = React.useState<boolean>(false); |
| 23 | const [isSidebarOpen, setSidebarOpen] = React.useState<boolean>(false); | 25 | const [isSidebarOpen, setSidebarOpen] = React.useState<boolean>(false); |
| 24 | const [selectedButtonIndex, setSelectedButtonIndex] = React.useState<number>(1); | 26 | const [selectedButtonIndex, setSelectedButtonIndex] = React.useState<number>(1); |
| 25 | 27 | ||
| 26 | const location = useLocation(); | 28 | const location = useLocation(); |
| 27 | const path = location.pathname; | 29 | const path = location.pathname; |
| 28 | 30 | ||
| 29 | const sidebarRef = useRef<HTMLDivElement>(null); | ||
| 30 | const sidebarButtonRefs = useRef<(HTMLButtonElement | null)[]>([]); | ||
| 31 | |||
| 32 | // const _handle_sidebar_toggle = useCallback(() => { | ||
| 33 | // if (!sidebarRef.current) return; | ||
| 34 | |||
| 35 | // if (isSidebarOpen) { | ||
| 36 | // setSidebarOpen(false); | ||
| 37 | // } else { | ||
| 38 | // setSidebarOpen(true); | ||
| 39 | // searchbarRef.current?.focus(); | ||
| 40 | // } | ||
| 41 | // }, [isSidebarOpen]); | ||
| 42 | |||
| 43 | const handle_sidebar_click = useCallback( | 31 | const handle_sidebar_click = useCallback( |
| 44 | (clicked_sidebar_idx: number) => { | 32 | (clicked_sidebar_idx: number) => { |
| 45 | setSelectedButtonIndex(clicked_sidebar_idx); | 33 | setSelectedButtonIndex(clicked_sidebar_idx); |
| 46 | if (isSidebarOpen) { | 34 | |
| 47 | setSidebarOpen(false); | 35 | if (clicked_sidebar_idx == 0 && !isSearching) { |
| 36 | if (!isSearching) { | ||
| 37 | setIsSearching(true); | ||
| 38 | } | ||
| 39 | } else { | ||
| 40 | setIsSearching(false); | ||
| 48 | } | 41 | } |
| 49 | }, | 42 | }, |
| 50 | [isSidebarOpen] | 43 | [isSearching] |
| 51 | ); | 44 | ); |
| 52 | 45 | ||
| 53 | React.useEffect(() => { | 46 | React.useEffect(() => { |
| 54 | if (path === "/") { | 47 | links.content.forEach((link, i) => { |
| 55 | setSelectedButtonIndex(1); | 48 | if (path.includes(link.to)) { |
| 56 | } else if (path.includes("games")) { | 49 | handle_sidebar_click(i + 1); |
| 57 | setSelectedButtonIndex(2); | 50 | } |
| 58 | } else if (path.includes("rankings")) { | 51 | }) |
| 59 | setSelectedButtonIndex(3); | ||
| 60 | } else if (path.includes("profile")) { | ||
| 61 | setSelectedButtonIndex(4); | ||
| 62 | } else if (path.includes("rules")) { | ||
| 63 | setSelectedButtonIndex(5); | ||
| 64 | } else if (path.includes("about")) { | ||
| 65 | setSelectedButtonIndex(6); | ||
| 66 | } | ||
| 67 | }, [path]); | ||
| 68 | |||
| 69 | const getButtonClasses = (buttonIndex: number) => { | ||
| 70 | const baseClasses = "flex items-center gap-3 w-full text-left bg-inherit cursor-pointer border-none rounded-[2000px] py-3 px-3 transition-all duration-300 hover:bg-panel"; | ||
| 71 | const selectedClasses = selectedButtonIndex === buttonIndex ? "bg-primary text-background" : "bg-transparent text-foreground"; | ||
| 72 | 52 | ||
| 73 | return `${baseClasses} ${selectedClasses}`; | 53 | links.footer.forEach((link, i) => { |
| 74 | }; | 54 | if (path.includes(link.to)) { |
| 55 | handle_sidebar_click(links.content.length + i + 1); | ||
| 56 | } | ||
| 57 | }) | ||
| 58 | }, [path]); | ||
| 75 | 59 | ||
| 76 | return ( | 60 | return ( |
| 77 | <div className={`w-80 not-md:w-full text-white bg-block flex flex-col not-md:flex-row | 61 | <div className={`h-screen w-80 not-md:w-full text-white bg-block flex flex-col not-md:flex-row not-md:bg-gradient-to-t not-md:from-block not-md:to-bright |
| 78 | }`}> | 62 | }`}> |
| 79 | 63 | ||
| 80 | {/* Header */} | 64 | {/* Header */} |
| 81 | <Header /> | 65 | <_Header /> |
| 82 | 66 | ||
| 83 | <div className="flex h-full w-full"> | 67 | <div className="flex flex-1 overflow-hidden w-full not-md:hidden "> |
| 84 | <div className="flex flex-col"> | 68 | <div className={`flex flex-col transition-all duration-300 ${isSearching ? "w-[64px]" : "w-full"}`}> |
| 85 | {/* Sidebar Content */} | 69 | {/* Sidebar Content */} |
| 86 | <Content profile={profile} isSidebarOpen={isSidebarOpen} sidebarButtonRefs={sidebarButtonRefs} getButtonClasses={getButtonClasses} handle_sidebar_click={handle_sidebar_click} /> | 70 | <_Content profile={profile} isSearching={isSearching} selectedButtonIndex={selectedButtonIndex} isSidebarOpen={isSidebarOpen} handle_sidebar_click={handle_sidebar_click} /> |
| 87 | 71 | ||
| 88 | {/* Bottom Section */} | 72 | {/* Bottom Section */} |
| 89 | <Footer profile={profile} onUploadRun={onUploadRun} setToken={setToken} setProfile={setProfile} sidebarButtonRefs={sidebarButtonRefs} getButtonClasses={getButtonClasses} handle_sidebar_click={handle_sidebar_click} /> | 73 | <_Footer profile={profile} isSearching={isSearching} selectedButtonIndex={selectedButtonIndex} onUploadRun={onUploadRun} setToken={setToken} setProfile={setProfile} handle_sidebar_click={handle_sidebar_click} /> |
| 90 | </div> | 74 | </div> |
| 91 | 75 | ||
| 92 | <div className="w-20"> | 76 | <div className={`flex bg-panel ${isSearching ? 'w-full' : "w-0"}`}> |
| 93 | 77 | <_Search profile={profile} isSearching={isSearching} /> | |
| 94 | </div> | 78 | </div> |
| 95 | 79 | ||
| 96 | </div> | 80 | </div> |