feat(category): dynamic dropdown item height

This commit is contained in:
enes 2024-12-04 13:26:22 +01:00
parent 4bf84cd9a6
commit 836d5b76e1
2 changed files with 89 additions and 41 deletions

View File

@ -1,6 +1,7 @@
import _ from 'lodash'
import { Event, kinds, nip19, UnsignedEvent } from 'nostr-tools'
import React, {
CSSProperties,
Fragment,
useCallback,
useEffect,
@ -10,7 +11,7 @@ import React, {
} from 'react'
import { Link, useLocation, useNavigate } from 'react-router-dom'
import { toast } from 'react-toastify'
import { FixedSizeList as List } from 'react-window'
import { VariableSizeList, FixedSizeList } from 'react-window'
import { v4 as uuidv4 } from 'uuid'
import { T_TAG_VALUE } from '../constants'
import { useAppSelector, useGames, useNDKContext } from '../hooks'
@ -865,7 +866,7 @@ const GameDropdown = ({
</svg>
</button>
<div className='dropdown-menu dropdownMainMenu dropdownMainMenuAlt'>
<List
<FixedSizeList
height={500}
width={'100%'}
itemCount={filteredOptions.length}
@ -887,7 +888,7 @@ const GameDropdown = ({
{filteredOptions[index].label}
</div>
)}
</List>
</FixedSizeList>
</div>
</div>
</div>
@ -992,6 +993,70 @@ export const CategoryAutocomplete = ({
}))
}, [selectedCategories, setFormState])
const listRef = useRef<VariableSizeList>(null)
const rowHeights = useRef<{ [index: number]: number }>({})
const setRowHeight = (index: number, size: number) => {
rowHeights.current = { ...rowHeights.current, [index]: size }
if (listRef.current) {
listRef.current.resetAfterIndex(index)
}
}
const getRowHeight = (index: number) => {
return (rowHeights.current[index] || 35) + 8
}
const Row = ({ index, style }: { index: number; style: CSSProperties }) => {
const rowRef = useRef<HTMLDivElement>(null)
useEffect(() => {
const rowElement = rowRef.current
if (!rowElement) return
const updateHeight = () => {
const height = Math.max(rowElement.scrollHeight, 35)
setRowHeight(index, height)
}
const observer = new ResizeObserver(() => {
updateHeight()
})
observer.observe(rowElement)
updateHeight()
return () => {
observer.disconnect()
}
}, [index])
if (!filteredOptions) return null
return (
<div
ref={rowRef}
style={style}
className='dropdown-item dropdownMainMenuItem'
onClick={() => handleSelect(filteredOptions[index])}
>
{capitalizeEachWord(filteredOptions[index].hierarchy)}
{selectedCategories.some(
(cat) => cat.hierarchy === filteredOptions[index].hierarchy
) && (
<button
className='btn btnMain btnMainInsideField btnMainRemove'
onClick={() => handleRemove(filteredOptions[index])}
>
<svg
xmlns='http://www.w3.org/2000/svg'
viewBox='-32 0 512 512'
width='1em'
height='1em'
fill='currentColor'
>
<path d='M323.3 32.01H188.7C172.3 32.01 160 44.31 160 60.73V96.01H32C14.33 96.01 0 110.3 0 128S14.33 160 32 160H480c17.67 0 32-14.33 32-32.01S497.7 96.01 480 96.01H352v-35.28C352 44.31 339.7 32.01 323.3 32.01zM64.9 477.5C66.5 492.3 79.31 504 94.72 504H417.3c15.41 0 28.22-11.72 29.81-26.5L480 192.2H32L64.9 477.5z'></path>
</svg>
</button>
)}
</div>
)
}
return (
<div className='inputLabelWrapperMain'>
<label className='form-label labelMain'>Categories</label>
@ -1025,43 +1090,22 @@ export const CategoryAutocomplete = ({
<div className='dropdown-menu dropdownMainMenu dropdownMainMenuAlt'>
{filteredOptions && filteredOptions.length > 0 ? (
<List
<VariableSizeList
ref={listRef}
height={500}
width={'100%'}
itemCount={filteredOptions.length}
itemSize={getRowHeight}
>
{Row}
</VariableSizeList>
) : (
<FixedSizeList
height={500}
width={'100%'}
itemCount={1}
itemSize={35}
>
{({ index, style }) => (
<div
style={style}
className='dropdown-item dropdownMainMenuItem'
onClick={() => handleSelect(filteredOptions[index])}
>
{capitalizeEachWord(filteredOptions[index].hierarchy)}
{selectedCategories.some(
(cat) =>
cat.hierarchy === filteredOptions[index].hierarchy
) && (
<button
className='btn btnMain btnMainInsideField btnMainRemove'
onClick={() => handleRemove(filteredOptions[index])}
>
<svg
xmlns='http://www.w3.org/2000/svg'
viewBox='-32 0 512 512'
width='1em'
height='1em'
fill='currentColor'
>
<path d='M323.3 32.01H188.7C172.3 32.01 160 44.31 160 60.73V96.01H32C14.33 96.01 0 110.3 0 128S14.33 160 32 160H480c17.67 0 32-14.33 32-32.01S497.7 96.01 480 96.01H352v-35.28C352 44.31 339.7 32.01 323.3 32.01zM64.9 477.5C66.5 492.3 79.31 504 94.72 504H417.3c15.41 0 28.22-11.72 29.81-26.5L480 192.2H32L64.9 477.5z'></path>
</svg>
</button>
)}
</div>
)}
</List>
) : (
<List height={500} width={'100%'} itemCount={1} itemSize={35}>
{({ index, style }) => (
<div
style={style}
@ -1093,7 +1137,7 @@ export const CategoryAutocomplete = ({
)}
</div>
)}
</List>
</FixedSizeList>
)}
</div>
</div>
@ -1103,8 +1147,9 @@ export const CategoryAutocomplete = ({
{LTags.map((hierarchy) => {
const heirarchicalCategories = hierarchy.split(`:`)
const categories = heirarchicalCategories
.map<React.ReactNode>((c: string) => (
.map<React.ReactNode>((c, i) => (
<Link
key={`category-${i}`}
to={{
pathname: getGamePageRoute(game),
search: `c=${c}`
@ -1114,9 +1159,12 @@ export const CategoryAutocomplete = ({
<p>{capitalizeEachWord(c)}</p>
</Link>
))
.reduce((prev, curr) => [
.reduce((prev, curr, i) => [
prev,
<div className='IBMSMSMBSSCategoriesBoxSeparator'>
<div
key={`separator-${i}`}
className='IBMSMSMBSSCategoriesBoxSeparator'
>
<p>&gt;</p>
</div>,
curr

View File

@ -298,7 +298,7 @@ h6 {
}
.dropdownMainMenuItem {
transition: ease 0.4s;
transition: background ease 0.4s, color ease 0.4s;
background: linear-gradient(
rgba(255, 255, 255, 0.03),
rgba(255, 255, 255, 0.03)
@ -319,7 +319,7 @@ h6 {
}
.dropdownMainMenuItem:hover {
transition: ease 0.4s;
transition: background ease 0.4s, color ease 0.4s;
background: linear-gradient(
rgba(255, 255, 255, 0.05),
rgba(255, 255, 255, 0.05)