Use custom dropdown

This commit is contained in:
Lncvrt committed 2026-05-30 12:56:28 -07:00
1 parent bde6b252d7
commit ca02d85b12
5 files changed
+161 -40

No files matched your search

+11 -11
View File
@@ -6,6 +6,7 @@ import { DiscordButton } from '../../components/DiscordButton'
import { useEffect, useRef, useState } from 'react' import { useEffect, useRef, useState } from 'react'
import axios from 'axios' import axios from 'axios'
import { UAParser } from 'ua-parser-js' import { UAParser } from 'ua-parser-js'
import Dropdown from '@/components/Dropdown'
export default function DownloadPage () { export default function DownloadPage () {
const [platform, setPlatform] = useState<string>('') const [platform, setPlatform] = useState<string>('')
@@ -67,18 +68,17 @@ export default function DownloadPage () {
The official downloads for Lncvrt Games Launcher The official downloads for Lncvrt Games Launcher
</p> </p>
<div className='flex justify-center'> <div className='flex justify-center'>
<select <Dropdown
value={platform} value={platform}
onChange={e => setPlatform(e.target.value)} options={[
className='' { label: 'Windows', value: 'windows' },
> { label: 'macOS', value: 'macos' },
<option value=''>Select platform</option> { label: 'Linux', value: 'linux' },
<option value='windows'>Windows</option> { label: 'Android', value: 'android' },
<option value='macos'>macOS</option> { label: 'iOS', value: 'ios' }
<option value='linux'>Linux</option> ]}
<option value='android'>Android</option> onChange={val => setPlatform(String(val))}
<option value='ios'>iOS</option> />
</select>
</div> </div>
{(platform == 'windows' || platform == 'macos' || platform == 'linux') && {(platform == 'windows' || platform == 'macos' || platform == 'linux') &&
(version === null || version === -1 ? ( (version === null || version === -1 ? (
+13 -13
View File
@@ -4,6 +4,7 @@ import { HomeButton } from '../../components/HomeButton'
import { DiscordButton } from '../../components/DiscordButton' import { DiscordButton } from '../../components/DiscordButton'
import { useEffect, useRef, useState } from 'react' import { useEffect, useRef, useState } from 'react'
import axios from 'axios' import axios from 'axios'
import Dropdown from '@/components/Dropdown'
interface Version { interface Version {
id: string id: string
@@ -98,19 +99,18 @@ export default function GameDownloadsPage () {
<> <>
<div> <div>
<p className='text-center'>Game Filter</p> <p className='text-center'>Game Filter</p>
<div className='flex justify-center mb-4'> <div className='flex justify-center mb-2'>
<select <Dropdown
className='w-full max-w-xs' value={String(game)}
value={game} options={[
onChange={e => setGame(Number(e.target.value))} { label: 'Off', value: '-1' },
> ...versionManifest.games.map(g => ({
<option value='-1'>Off</option> label: g.name,
{versionManifest.games.map(g => ( value: String(g.id)
<option key={g.id} value={g.id}> }))
{g.name} ]}
</option> onChange={val => setGame(Number(val))}
))} />
</select>
</div> </div>
</div> </div>
<div className='flex flex-col gap-2'> <div className='flex flex-col gap-2'>
+17 -16
View File
@@ -9,6 +9,7 @@ import axios from 'axios'
import { GetIconForUser } from '@/util/bd' import { GetIconForUser } from '@/util/bd'
import Image from 'next/image' import Image from 'next/image'
import { useSearchParams } from 'next/navigation' import { useSearchParams } from 'next/navigation'
import Dropdown from '@/components/Dropdown'
interface BaseEntry { interface BaseEntry {
id: number id: number
@@ -267,22 +268,22 @@ function BerryDashLeaderboardsSuspence () {
})} })}
</div> </div>
{selected == 1 && ( {selected == 1 && (
<div className='flex justify-center'> <div className='flex justify-center -mb-4 mt-2'>
<select <Dropdown
value={selectedBerryOption} value={String(selectedBerryOption)}
onChange={e => setSelectedBerryOption(Number(e.target.value))} options={[
className='-my-4 mt-2' { label: 'Normal Berry', value: '0' },
> { label: 'Poison Berry', value: '1' },
<option value='0'>Normal Berry</option> { label: 'Slow Berry', value: '2' },
<option value='1'>Poison Berry</option> { label: 'Ultra Berry', value: '3' },
<option value='2'>Slow Berry</option> { label: 'Speedy Berry', value: '4' },
<option value='3'>Ultra Berry</option> { label: 'Coin Berry', value: '5' },
<option value='4'>Speedy Berry</option> { label: 'Random Berry', value: '6' },
<option value='5'>Coin Berry</option> { label: 'Anti Berry', value: '7' },
<option value='6'>Random Berry</option> { label: 'Golden Berry', value: '8' }
<option value='7'>Anti Berry</option> ]}
<option value='8'>Golden Berry</option> onChange={val => setSelectedBerryOption(Number(val))}
</select> />
</div> </div>
)} )}
</> </>
+61
View File
@@ -0,0 +1,61 @@
import { useState, useRef, useEffect } from 'react'
import '@/styles/dropdown.css'
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
import { faChevronDown } from '@fortawesome/free-solid-svg-icons'
type Option = {
label: string
value: string | number
}
type Props = {
value: string | number
options: Option[]
onChange: (value: string | number) => void | Promise<void>
}
export default function Dropdown ({ value, options, onChange }: Props) {
const [open, setOpen] = useState(false)
const ref = useRef<HTMLDivElement>(null)
const selected = options.find(o => o.value === value)
useEffect(() => {
const handleClick = (e: MouseEvent) => {
if (!ref.current?.contains(e.target as Node)) {
setOpen(false)
}
}
window.addEventListener('mousedown', handleClick)
return () => window.removeEventListener('mousedown', handleClick)
}, [])
return (
<div ref={ref} className='dropdown'>
<button className='dropdown-btn' onClick={() => setOpen(o => !o)}>
<span>{selected?.label ?? 'Select...'}</span>
<FontAwesomeIcon
icon={faChevronDown}
className={`dropdown-arrow ${open ? 'open' : ''}`}
/>
</button>
<div className={`dropdown-menu ${open ? 'open' : ''}`}>
{options.map((option, i) => (
<div
key={option.value}
className={`dropdown-item ${
option.value === value ? 'active' : ''
}`}
style={{ ['--i']: i } as React.CSSProperties}
onClick={async () => {
await onChange(option.value)
setOpen(false)
}}
>
{option.label}
</div>
))}
</div>
</div>
)
}
+59
View File
@@ -0,0 +1,59 @@
@import "tailwindcss";
.dropdown {
@apply relative w-40;
}
.dropdown-btn {
@apply w-full flex items-center justify-between px-2 py-1 rounded-md cursor-pointer
bg-(--col2) hover:bg-(--col3)
border border-(--col4) hover:border-(--col5);
}
.dropdown-arrow {
@apply w-3 h-3 transition-transform stroke-current stroke-2 fill-none;
}
.dropdown-arrow.open {
@apply rotate-180;
}
.dropdown-menu {
@apply absolute z-50 mt-1 w-full rounded-md shadow-2xl overflow-hidden
bg-(--col2) border border-(--col4)
origin-top
opacity-0 scale-95 -translate-y-1 pointer-events-none
transition-[opacity,transform] duration-150 ease-out;
}
.dropdown-menu.open {
@apply opacity-100 scale-100 translate-y-0 pointer-events-auto;
}
.dropdown-item {
@apply px-2 py-1 cursor-pointer transition-colors border-b border-(--col4) last:border-b-0;
opacity: 0;
transform: translateY(-10px);
}
.dropdown-item:hover,
.dropdown-item.active {
@apply bg-(--col3);
}
.dropdown-menu.open .dropdown-item {
animation: dropIn 220ms ease-out forwards;
animation-delay: calc(var(--i) * 40ms);
}
@keyframes dropIn {
from {
opacity: 0;
transform: translateY(-10px);
}
to {
opacity: 1;
transform: translateY(0);
}
}