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 { 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 ? (
|
||||||
|
|||||||
@@ -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'>
|
||||||
|
|||||||
@@ -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>
|
||||||
)}
|
)}
|
||||||
</>
|
</>
|
||||||
|
|||||||
@@ -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