Fix search filters

* Update styles for all search filters
* Make search filters function better on mobile
* Small refactor on individual filter bar files to extract individual search filter rendering into variables
This commit is contained in:
Justin Edmund 2023-06-30 12:25:33 -07:00
parent 3434e7042c
commit c26752d8c1
12 changed files with 386 additions and 366 deletions

View file

@ -0,0 +1,11 @@
.filterBar {
display: flex;
gap: $unit;
padding: $unit-half $unit-3x;
@include breakpoint(phone) {
display: grid;
gap: $unit;
grid-template-columns: 1fr 1fr;
}
}

View file

@ -144,121 +144,90 @@ const CharacterSearchFilterBar = (props: Props) => {
return ( return (
<SearchFilter <SearchFilter
label={`${t('filters.labels.proficiency')} ${proficiency}`} label={`${t('filters.labels.proficiency')} ${proficiency}`}
display="grid"
numSelected={numSelected} numSelected={numSelected}
open={open} open={open}
onOpenChange={onOpenChange} onOpenChange={onOpenChange}
> >
<DropdownMenu.Label className="Label">{`${t( {Array.from(Array(proficiencies.length)).map((x, i) => {
'filters.labels.proficiency' const checked =
)} ${proficiency}`}</DropdownMenu.Label> proficiency == 1
<section> ? proficiency1State[proficiencies[i]].checked
<DropdownMenu.Group className="Group"> : proficiency2State[proficiencies[i]].checked
{Array.from(Array(proficiencies.length / 2)).map((x, i) => {
const checked =
proficiency == 1
? proficiency1State[proficiencies[i]].checked
: proficiency2State[proficiencies[i]].checked
return ( return (
<SearchFilterCheckboxItem <SearchFilterCheckboxItem
key={proficiencies[i]} key={proficiencies[i]}
onCheckedChange={onCheckedChange} onCheckedChange={onCheckedChange}
checked={checked} checked={checked}
valueKey={proficiencies[i]} valueKey={proficiencies[i]}
> >
{t(`proficiencies.${proficiencies[i]}`)} {t(`proficiencies.${proficiencies[i]}`)}
</SearchFilterCheckboxItem> </SearchFilterCheckboxItem>
) )
})} })}
</DropdownMenu.Group>
<DropdownMenu.Group className="Group">
{Array.from(Array(proficiencies.length / 2)).map((x, i) => {
const checked =
proficiency == 1
? proficiency1State[
proficiencies[i + proficiencies.length / 2]
].checked
: proficiency2State[
proficiencies[i + proficiencies.length / 2]
].checked
return (
<SearchFilterCheckboxItem
key={proficiencies[i + proficiencies.length / 2]}
onCheckedChange={onCheckedChange}
checked={checked}
valueKey={proficiencies[i + proficiencies.length / 2]}
>
{t(
`proficiencies.${
proficiencies[i + proficiencies.length / 2]
}`
)}
</SearchFilterCheckboxItem>
)
})}
</DropdownMenu.Group>
</section>
</SearchFilter> </SearchFilter>
) )
} }
const rarityFilter = (
<SearchFilter
label={t('filters.labels.rarity')}
display="list"
numSelected={
Object.values(rarityState)
.map((x) => x.checked)
.filter(Boolean).length
}
open={rarityMenu}
onOpenChange={rarityMenuOpened}
>
{Array.from(Array(rarities.length)).map((x, i) => {
return (
<SearchFilterCheckboxItem
key={rarities[i]}
onCheckedChange={handleRarityChange}
checked={rarityState[rarities[i]].checked}
valueKey={rarities[i]}
>
{t(`rarities.${rarities[i]}`)}
</SearchFilterCheckboxItem>
)
})}
</SearchFilter>
)
const elementFilter = (
<SearchFilter
label={t('filters.labels.element')}
display="list"
numSelected={
Object.values(elementState)
.map((x) => x.checked)
.filter(Boolean).length
}
open={elementMenu}
onOpenChange={elementMenuOpened}
>
{Array.from(Array(elements.length)).map((x, i) => {
return (
<SearchFilterCheckboxItem
key={elements[i]}
onCheckedChange={handleElementChange}
checked={elementState[elements[i]].checked}
valueKey={elements[i]}
>
{t(`elements.${elements[i]}`)}
</SearchFilterCheckboxItem>
)
})}
</SearchFilter>
)
return ( return (
<div className="SearchFilterBar"> <div className={styles.filterBar}>
<SearchFilter {rarityFilter}
label={t('filters.labels.rarity')} {elementFilter}
numSelected={
Object.values(rarityState)
.map((x) => x.checked)
.filter(Boolean).length
}
open={rarityMenu}
onOpenChange={rarityMenuOpened}
>
<DropdownMenu.Label className="Label">
{t('filters.labels.rarity')}
</DropdownMenu.Label>
{Array.from(Array(rarities.length)).map((x, i) => {
return (
<SearchFilterCheckboxItem
key={rarities[i]}
onCheckedChange={handleRarityChange}
checked={rarityState[rarities[i]].checked}
valueKey={rarities[i]}
>
{t(`rarities.${rarities[i]}`)}
</SearchFilterCheckboxItem>
)
})}
</SearchFilter>
<SearchFilter
label={t('filters.labels.element')}
numSelected={
Object.values(elementState)
.map((x) => x.checked)
.filter(Boolean).length
}
open={elementMenu}
onOpenChange={elementMenuOpened}
>
<DropdownMenu.Label className="Label">
{t('filters.labels.element')}
</DropdownMenu.Label>
{Array.from(Array(elements.length)).map((x, i) => {
return (
<SearchFilterCheckboxItem
key={elements[i]}
onCheckedChange={handleElementChange}
checked={elementState[elements[i]].checked}
valueKey={elements[i]}
>
{t(`elements.${elements[i]}`)}
</SearchFilterCheckboxItem>
)
})}
</SearchFilter>
{renderProficiencyFilter(1)} {renderProficiencyFilter(1)}
{renderProficiencyFilter(2)} {renderProficiencyFilter(2)}
</div> </div>

View file

@ -1,3 +1,9 @@
.SearchFilterBar .SelectTrigger { .SearchFilterBar .SelectTrigger {
width: 100%; width: 100%;
} }
.filterBar {
display: flex;
gap: $unit;
padding: $unit-half $unit-3x;
}

View file

@ -39,10 +39,13 @@ const JobSkillSearchFilterBar = (props: Props) => {
}, [currentGroup]) }, [currentGroup])
return ( return (
<div className="SearchFilterBar"> <div className={styles.filterBar}>
<Select <Select
value={-1} value={-1}
triggerClass="Bound" trigger={{
bound: true,
className: 'full',
}}
open={open} open={open}
overlayVisible={false} overlayVisible={false}
onValueChange={onChange} onValueChange={onChange}

View file

@ -1,35 +1,54 @@
button.DropdownLabel { .pill {
align-items: center; align-items: center;
background: var(--button-contained-bg); background: var(--button-contained-bg);
border: none; border: none;
border-radius: $unit-2x; border-radius: $unit-2x;
color: var(--text-secondary); border: 2px solid transparent;
display: flex; display: flex;
font-size: $font-small; font-size: $font-small;
gap: $unit-half; gap: $unit-half;
flex-direction: row; flex-direction: row;
justify-content: space-between; justify-content: space-between;
padding: $unit ($unit * 1.5) $unit $unit-2x; padding: $unit ($unit * 1.5);
div {
align-items: center;
display: flex;
gap: $unit-half;
}
&:hover { &:hover {
background: var(--button-contained-bg-hover); background: var(--button-contained-bg-hover);
color: var(--text-primary);
cursor: pointer; cursor: pointer;
.text {
color: var(--text-primary);
}
} }
.count { @include breakpoint(phone) {
color: var(--text-tertiary); justify-content: center;
font-weight: $medium;
} }
& > .icon { .label {
$diameter: 16px; justify-content: center;
flex: 1;
@include breakpoint(phone) {
&::before {
content: '';
display: block;
width: calc($unit-2x - 1px);
}
}
.text {
color: var(--text-tertiary);
font-weight: $bold;
}
.count {
color: var(--text-secondary);
font-weight: $medium;
}
}
.icon {
$diameter: $unit-2x;
height: $diameter; height: $diameter;
width: $diameter; width: $diameter;
@ -39,16 +58,39 @@ button.DropdownLabel {
} }
} }
} }
div {
align-items: center;
display: flex;
gap: $unit-half;
}
} }
.Dropdown { .dropdown {
background: var(--button-contained-bg); background: var(--button-contained-bg);
border: 1px solid rgba(0, 0, 0, 0.1);
border-radius: $unit; border-radius: $unit;
box-shadow: 0 0 2px rgba(0, 0, 0, 0.18); box-shadow: 0 0 4px rgba(0, 0, 0, 0.18);
display: flex; display: flex;
flex-direction: column; flex-direction: column;
gap: $unit;
padding: $unit; padding: $unit;
min-width: 120px; overflow-y: scroll;
max-height: 60vh;
@include breakpoint(phone) {
width: calc(100vw - $unit-6x);
}
.grid {
display: grid;
grid-template-columns: 1fr 1fr;
}
.list {
display: flex;
flex-direction: column;
}
& > span { & > span {
overflow: hidden; overflow: hidden;
@ -64,16 +106,4 @@ button.DropdownLabel {
flex-direction: row; flex-direction: row;
gap: $unit; gap: $unit;
} }
.Group {
flex: 1 1 0px;
flex-direction: column;
}
.Label {
color: var(--text-tertiary);
font-size: $font-small;
margin-bottom: $unit-half;
padding: $unit-half 0 $unit $unit-half;
}
} }

View file

@ -1,5 +1,5 @@
import React from 'react' import React from 'react'
import classNames from 'classnames'
import * as DropdownMenu from '@radix-ui/react-dropdown-menu' import * as DropdownMenu from '@radix-ui/react-dropdown-menu'
import ChevronIcon from '~public/icons/Chevron.svg' import ChevronIcon from '~public/icons/Chevron.svg'
@ -8,26 +8,35 @@ import styles from './index.module.scss'
interface Props { interface Props {
label: string label: string
open: boolean open: boolean
display: 'grid' | 'list'
numSelected: number numSelected: number
onOpenChange: (open: boolean) => void onOpenChange: (open: boolean) => void
children: React.ReactNode children: React.ReactNode
} }
const SearchFilter = (props: Props) => { const SearchFilter = (props: Props) => {
const displayClasses = classNames({
[styles.grid]: props.display === 'grid',
[styles.list]: props.display === 'list',
})
return ( return (
<DropdownMenu.Root open={props.open} onOpenChange={props.onOpenChange}> <DropdownMenu.Root open={props.open} onOpenChange={props.onOpenChange}>
<DropdownMenu.Trigger className="DropdownLabel"> <DropdownMenu.Trigger className={styles.pill}>
<div> <div className={styles.label}>
{props.label} <span className={styles.text}>{props.label}</span>
<span className="count">{props.numSelected}</span> <span className={styles.count}>{props.numSelected}</span>
</div> </div>
<span className="icon"> <span className={styles.icon}>
<ChevronIcon /> <ChevronIcon />
</span> </span>
</DropdownMenu.Trigger> </DropdownMenu.Trigger>
<DropdownMenu.Content className="Dropdown" sideOffset={4}> <DropdownMenu.Content
{props.children} className={styles.dropdown}
<DropdownMenu.Arrow /> sideOffset={4}
collisionPadding={16}
>
<div className={displayClasses}>{props.children}</div>
</DropdownMenu.Content> </DropdownMenu.Content>
</DropdownMenu.Root> </DropdownMenu.Root>
) )

View file

@ -1,7 +1,7 @@
.Item { .item {
align-items: center; align-items: center;
border-radius: calc($unit / 2); border-radius: calc($unit / 2);
color: var(--text-secondary); color: var(--text-tertiary);
font-size: $font-regular; font-size: $font-regular;
line-height: 1.2; line-height: 1.2;
min-width: 100px; min-width: 100px;
@ -23,7 +23,7 @@
} }
} }
.Indicator { .indicator {
$diameter: 20px; $diameter: 20px;
display: flex; display: flex;

View file

@ -23,12 +23,12 @@ const SearchFilterCheckboxItem = (props: Props) => {
return ( return (
<DropdownMenu.CheckboxItem <DropdownMenu.CheckboxItem
className="Item" className={styles.item}
checked={props.checked || false} checked={props.checked || false}
onCheckedChange={handleCheckedChange} onCheckedChange={handleCheckedChange}
onSelect={(event) => event.preventDefault()} onSelect={(event) => event.preventDefault()}
> >
<DropdownMenu.ItemIndicator className="Indicator"> <DropdownMenu.ItemIndicator className={styles.indicator}>
<CheckIcon /> <CheckIcon />
</DropdownMenu.ItemIndicator> </DropdownMenu.ItemIndicator>
{props.children} {props.children}

View file

@ -0,0 +1,11 @@
.filterBar {
display: flex;
gap: $unit;
padding: $unit-half $unit-3x;
@include breakpoint(phone) {
display: grid;
gap: $unit;
grid-template-columns: 1fr 1fr;
}
}

View file

@ -72,61 +72,64 @@ const SummonSearchFilterBar = (props: Props) => {
sendFilters() sendFilters()
}, [rarityState, elementState]) }, [rarityState, elementState])
return ( const rarityFilter = (
<div className="SearchFilterBar"> <SearchFilter
<SearchFilter label={t('filters.labels.rarity')}
label={t('filters.labels.rarity')} display="list"
numSelected={ numSelected={
Object.values(rarityState) Object.values(rarityState)
.map((x) => x.checked) .map((x) => x.checked)
.filter(Boolean).length .filter(Boolean).length
} }
open={rarityMenu} open={rarityMenu}
onOpenChange={rarityMenuOpened} onOpenChange={rarityMenuOpened}
> >
<DropdownMenu.Label className="Label"> {Array.from(Array(rarities.length)).map((x, i) => {
{t('filters.labels.rarity')} return (
</DropdownMenu.Label> <SearchFilterCheckboxItem
{Array.from(Array(rarities.length)).map((x, i) => { key={rarities[i]}
return ( onCheckedChange={handleRarityChange}
<SearchFilterCheckboxItem checked={rarityState[rarities[i]].checked}
key={rarities[i]} valueKey={rarities[i]}
onCheckedChange={handleRarityChange} >
checked={rarityState[rarities[i]].checked} {t(`rarities.${rarities[i]}`)}
valueKey={rarities[i]} </SearchFilterCheckboxItem>
> )
{t(`rarities.${rarities[i]}`)} })}
</SearchFilterCheckboxItem> </SearchFilter>
) )
})}
</SearchFilter>
<SearchFilter const elementFilter = (
label={t('filters.labels.element')} <SearchFilter
numSelected={ label={t('filters.labels.element')}
Object.values(elementState) display="list"
.map((x) => x.checked) numSelected={
.filter(Boolean).length Object.values(elementState)
} .map((x) => x.checked)
open={elementMenu} .filter(Boolean).length
onOpenChange={elementMenuOpened} }
> open={elementMenu}
<DropdownMenu.Label className="Label"> onOpenChange={elementMenuOpened}
{t('filters.labels.element')} >
</DropdownMenu.Label> {Array.from(Array(elements.length)).map((x, i) => {
{Array.from(Array(elements.length)).map((x, i) => { return (
return ( <SearchFilterCheckboxItem
<SearchFilterCheckboxItem key={elements[i]}
key={elements[i]} onCheckedChange={handleElementChange}
onCheckedChange={handleElementChange} checked={elementState[elements[i]].checked}
checked={elementState[elements[i]].checked} valueKey={elements[i]}
valueKey={elements[i]} >
> {t(`elements.${elements[i]}`)}
{t(`elements.${elements[i]}`)} </SearchFilterCheckboxItem>
</SearchFilterCheckboxItem> )
) })}
})} </SearchFilter>
</SearchFilter> )
return (
<div className={styles.filterBar}>
{rarityFilter}
{elementFilter}
</div> </div>
) )
} }

View file

@ -0,0 +1,11 @@
.filterBar {
display: flex;
gap: $unit;
padding: $unit-half $unit-3x;
@include breakpoint(phone) {
display: grid;
gap: $unit;
grid-template-columns: 1fr 1fr;
}
}

View file

@ -40,6 +40,10 @@ const WeaponSearchFilterBar = (props: Props) => {
emptyWeaponSeriesState emptyWeaponSeriesState
) )
useEffect(() => {
sendFilters()
}, [rarityState, elementState, proficiencyState, seriesState])
function rarityMenuOpened(open: boolean) { function rarityMenuOpened(open: boolean) {
if (open) { if (open) {
setRarityMenu(true) setRarityMenu(true)
@ -124,178 +128,141 @@ const WeaponSearchFilterBar = (props: Props) => {
props.sendFilters(filters) props.sendFilters(filters)
} }
const renderWeaponSeries = () => { const renderProficiencies = () => {
const numColumns = 3
return ( return (
<React.Fragment> <>
{Array.from({ length: numColumns }, () => 0).map((x, i) => { {proficiencies.map((x, i) => {
return renderWeaponSeriesGroup(i) return (
})} <SearchFilterCheckboxItem
</React.Fragment> key={x}
) onCheckedChange={handleProficiencyChange}
} checked={proficiencyState[x].checked}
valueKey={x}
const renderWeaponSeriesGroup = (index: number) => { >
return ( {t(`proficiencies.${x}`)}
<DropdownMenu.Group className="Group" key={`Group-${index}`}> </SearchFilterCheckboxItem>
{weaponSeries
.slice(
index * Math.ceil(weaponSeries.length / 3),
(index + 1) * Math.ceil(weaponSeries.length / 3)
) )
.map((x, i) => { })}
return renderSingleWeaponSeries(x.id, x.slug) </>
})}
</DropdownMenu.Group>
) )
} }
const renderSingleWeaponSeries = (id: number, slug: string) => { const renderWeaponSeries = () => {
return ( return (
<SearchFilterCheckboxItem <>
key={slug} {weaponSeries.map((x, i) => {
onCheckedChange={handleSeriesChange} return (
checked={seriesState[slug].checked} <SearchFilterCheckboxItem
valueKey={slug} key={x.slug}
> onCheckedChange={handleSeriesChange}
{t(`series.${slug}`)} checked={seriesState[x.slug].checked}
</SearchFilterCheckboxItem> valueKey={x.slug}
>
{t(`series.${x.slug}`)}
</SearchFilterCheckboxItem>
)
})}
</>
) )
} }
useEffect(() => { const rarityFilter = (
sendFilters() <SearchFilter
}, [rarityState, elementState, proficiencyState, seriesState]) label={t('filters.labels.rarity')}
key="rarity"
display="list"
numSelected={
Object.values(rarityState)
.map((x) => x.checked)
.filter(Boolean).length
}
open={rarityMenu}
onOpenChange={rarityMenuOpened}
>
{Array.from(Array(rarities.length)).map((x, i) => {
return (
<SearchFilterCheckboxItem
key={rarities[i]}
onCheckedChange={handleRarityChange}
checked={rarityState[rarities[i]].checked}
valueKey={rarities[i]}
>
{t(`rarities.${rarities[i]}`)}
</SearchFilterCheckboxItem>
)
})}
</SearchFilter>
)
const elementFilter = (
<SearchFilter
label={t('filters.labels.element')}
key="element"
display="list"
numSelected={
Object.values(elementState)
.map((x) => x.checked)
.filter(Boolean).length
}
open={elementMenu}
onOpenChange={elementMenuOpened}
>
{Array.from(Array(elements.length)).map((x, i) => {
return (
<SearchFilterCheckboxItem
key={elements[i]}
onCheckedChange={handleElementChange}
checked={elementState[elements[i]].checked}
valueKey={elements[i]}
>
{t(`elements.${elements[i]}`)}
</SearchFilterCheckboxItem>
)
})}
</SearchFilter>
)
const proficiencyFilter = (
<SearchFilter
label={t('filters.labels.proficiency')}
key="proficiency"
display="grid"
numSelected={
Object.values(proficiencyState)
.map((x) => x.checked)
.filter(Boolean).length
}
open={proficiencyMenu}
onOpenChange={proficiencyMenuOpened}
>
{renderProficiencies()}
</SearchFilter>
)
const seriesFilter = (
<SearchFilter
label={t('filters.labels.series')}
key="series"
display="grid"
numSelected={
Object.values(seriesState)
.map((x) => x.checked)
.filter(Boolean).length
}
open={seriesMenu}
onOpenChange={seriesMenuOpened}
>
{renderWeaponSeries()}
</SearchFilter>
)
return ( return (
<div className="SearchFilterBar"> <section className={styles.filterBar}>
<SearchFilter {rarityFilter}
label={t('filters.labels.rarity')} {elementFilter}
key="rarity" {proficiencyFilter}
numSelected={ {seriesFilter}
Object.values(rarityState) </section>
.map((x) => x.checked)
.filter(Boolean).length
}
open={rarityMenu}
onOpenChange={rarityMenuOpened}
>
<DropdownMenu.Label className="Label">
{t('filters.labels.rarity')}
</DropdownMenu.Label>
{Array.from(Array(rarities.length)).map((x, i) => {
return (
<SearchFilterCheckboxItem
key={rarities[i]}
onCheckedChange={handleRarityChange}
checked={rarityState[rarities[i]].checked}
valueKey={rarities[i]}
>
{t(`rarities.${rarities[i]}`)}
</SearchFilterCheckboxItem>
)
})}
</SearchFilter>
<SearchFilter
label={t('filters.labels.element')}
key="element"
numSelected={
Object.values(elementState)
.map((x) => x.checked)
.filter(Boolean).length
}
open={elementMenu}
onOpenChange={elementMenuOpened}
>
<DropdownMenu.Label className="Label">
{t('filters.labels.element')}
</DropdownMenu.Label>
{Array.from(Array(elements.length)).map((x, i) => {
return (
<SearchFilterCheckboxItem
key={elements[i]}
onCheckedChange={handleElementChange}
checked={elementState[elements[i]].checked}
valueKey={elements[i]}
>
{t(`elements.${elements[i]}`)}
</SearchFilterCheckboxItem>
)
})}
</SearchFilter>
<SearchFilter
label={t('filters.labels.proficiency')}
key="proficiency"
numSelected={
Object.values(proficiencyState)
.map((x) => x.checked)
.filter(Boolean).length
}
open={proficiencyMenu}
onOpenChange={proficiencyMenuOpened}
>
<DropdownMenu.Label className="Label">
{t('filters.labels.proficiency')}
</DropdownMenu.Label>
<section>
<DropdownMenu.Group className="Group">
{Array.from(Array(proficiencies.length / 2)).map((x, i) => {
return (
<SearchFilterCheckboxItem
key={proficiencies[i]}
onCheckedChange={handleProficiencyChange}
checked={proficiencyState[proficiencies[i]].checked}
valueKey={proficiencies[i]}
>
{t(`proficiencies.${proficiencies[i]}`)}
</SearchFilterCheckboxItem>
)
})}
</DropdownMenu.Group>
<DropdownMenu.Group className="Group">
{Array.from(Array(proficiencies.length / 2)).map((x, i) => {
return (
<SearchFilterCheckboxItem
key={proficiencies[i + proficiencies.length / 2]}
onCheckedChange={handleProficiencyChange}
checked={
proficiencyState[
proficiencies[i + proficiencies.length / 2]
].checked
}
valueKey={proficiencies[i + proficiencies.length / 2]}
>
{t(
`proficiencies.${
proficiencies[i + proficiencies.length / 2]
}`
)}
</SearchFilterCheckboxItem>
)
})}
</DropdownMenu.Group>
</section>
</SearchFilter>
<SearchFilter
label={t('filters.labels.series')}
key="series"
numSelected={
Object.values(seriesState)
.map((x) => x.checked)
.filter(Boolean).length
}
open={seriesMenu}
onOpenChange={seriesMenuOpened}
>
<DropdownMenu.Label className="Label">
{t('filters.labels.series')}
</DropdownMenu.Label>
<section>{renderWeaponSeries()}</section>
</SearchFilter>
</div>
) )
} }