Use custom dropdown
This commit is contained in:
5 files changed
+161
-40
No files matched your search
+11
-11
@@ -6,6 +6,7 @@ import { DiscordButton } from '../../components/DiscordButton'
|
||||
import { useEffect, useRef, useState } from 'react'
|
||||
import axios from 'axios'
|
||||
import { UAParser } from 'ua-parser-js'
|
||||
import Dropdown from '@/components/Dropdown'
|
||||
|
||||
export default function DownloadPage () {
|
||||
const [platform, setPlatform] = useState<string>('')
|
||||
@@ -67,18 +68,17 @@ export default function DownloadPage () {
|
||||
The official downloads for Lncvrt Games Launcher
|
||||
</p>
|
||||
<div className='flex justify-center'>
|
||||
<select
|
||||
<Dropdown
|
||||
value={platform}
|
||||
onChange={e => setPlatform(e.target.value)}
|
||||
className=''
|
||||
>
|
||||
<option value=''>Select platform</option>
|
||||
<option value='windows'>Windows</option>
|
||||
<option value='macos'>macOS</option>
|
||||
<option value='linux'>Linux</option>
|
||||
<option value='android'>Android</option>
|
||||
<option value='ios'>iOS</option>
|
||||
</select>
|
||||
options={[
|
||||
{ label: 'Windows', value: 'windows' },
|
||||
{ label: 'macOS', value: 'macos' },
|
||||
{ label: 'Linux', value: 'linux' },
|
||||
{ label: 'Android', value: 'android' },
|
||||
{ label: 'iOS', value: 'ios' }
|
||||
]}
|
||||
onChange={val => setPlatform(String(val))}
|
||||
/>
|
||||
</div>
|
||||
{(platform == 'windows' || platform == 'macos' || platform == 'linux') &&
|
||||
(version === null || version === -1 ? (
|
||||
|
||||
@@ -4,6 +4,7 @@ import { HomeButton } from '../../components/HomeButton'
|
||||
import { DiscordButton } from '../../components/DiscordButton'
|
||||
import { useEffect, useRef, useState } from 'react'
|
||||
import axios from 'axios'
|
||||
import Dropdown from '@/components/Dropdown'
|
||||
|
||||
interface Version {
|
||||
id: string
|
||||
@@ -98,19 +99,18 @@ export default function GameDownloadsPage () {
|
||||
<>
|
||||
<div>
|
||||
<p className='text-center'>Game Filter</p>
|
||||
<div className='flex justify-center mb-4'>
|
||||
<select
|
||||
className='w-full max-w-xs'
|
||||
value={game}
|
||||
onChange={e => setGame(Number(e.target.value))}
|
||||
>
|
||||
<option value='-1'>Off</option>
|
||||
{versionManifest.games.map(g => (
|
||||
<option key={g.id} value={g.id}>
|
||||
{g.name}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
<div className='flex justify-center mb-2'>
|
||||
<Dropdown
|
||||
value={String(game)}
|
||||
options={[
|
||||
{ label: 'Off', value: '-1' },
|
||||
...versionManifest.games.map(g => ({
|
||||
label: g.name,
|
||||
value: String(g.id)
|
||||
}))
|
||||
]}
|
||||
onChange={val => setGame(Number(val))}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className='flex flex-col gap-2'>
|
||||
|
||||
@@ -9,6 +9,7 @@ import axios from 'axios'
|
||||
import { GetIconForUser } from '@/util/bd'
|
||||
import Image from 'next/image'
|
||||
import { useSearchParams } from 'next/navigation'
|
||||
import Dropdown from '@/components/Dropdown'
|
||||
|
||||
interface BaseEntry {
|
||||
id: number
|
||||
@@ -267,22 +268,22 @@ function BerryDashLeaderboardsSuspence () {
|
||||
})}
|
||||
</div>
|
||||
{selected == 1 && (
|
||||
<div className='flex justify-center'>
|
||||
<select
|
||||
value={selectedBerryOption}
|
||||
onChange={e => setSelectedBerryOption(Number(e.target.value))}
|
||||
className='-my-4 mt-2'
|
||||
>
|
||||
<option value='0'>Normal Berry</option>
|
||||
<option value='1'>Poison Berry</option>
|
||||
<option value='2'>Slow Berry</option>
|
||||
<option value='3'>Ultra Berry</option>
|
||||
<option value='4'>Speedy Berry</option>
|
||||
<option value='5'>Coin Berry</option>
|
||||
<option value='6'>Random Berry</option>
|
||||
<option value='7'>Anti Berry</option>
|
||||
<option value='8'>Golden Berry</option>
|
||||
</select>
|
||||
<div className='flex justify-center -mb-4 mt-2'>
|
||||
<Dropdown
|
||||
value={String(selectedBerryOption)}
|
||||
options={[
|
||||
{ label: 'Normal Berry', value: '0' },
|
||||
{ label: 'Poison Berry', value: '1' },
|
||||
{ label: 'Slow Berry', value: '2' },
|
||||
{ label: 'Ultra Berry', value: '3' },
|
||||
{ label: 'Speedy Berry', value: '4' },
|
||||
{ label: 'Coin Berry', value: '5' },
|
||||
{ label: 'Random Berry', value: '6' },
|
||||
{ label: 'Anti Berry', value: '7' },
|
||||
{ label: 'Golden Berry', value: '8' }
|
||||
]}
|
||||
onChange={val => setSelectedBerryOption(Number(val))}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
|
||||
@@ -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>
|
||||
)
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user