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,16 +144,12 @@ 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'
)} ${proficiency}`}</DropdownMenu.Label>
<section>
<DropdownMenu.Group className="Group">
{Array.from(Array(proficiencies.length / 2)).map((x, i) => {
const checked = const checked =
proficiency == 1 proficiency == 1
? proficiency1State[proficiencies[i]].checked ? proficiency1State[proficiencies[i]].checked
@ -170,43 +166,14 @@ const CharacterSearchFilterBar = (props: Props) => {
</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>
) )
} }
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)
@ -215,9 +182,6 @@ const CharacterSearchFilterBar = (props: Props) => {
open={rarityMenu} open={rarityMenu}
onOpenChange={rarityMenuOpened} onOpenChange={rarityMenuOpened}
> >
<DropdownMenu.Label className="Label">
{t('filters.labels.rarity')}
</DropdownMenu.Label>
{Array.from(Array(rarities.length)).map((x, i) => { {Array.from(Array(rarities.length)).map((x, i) => {
return ( return (
<SearchFilterCheckboxItem <SearchFilterCheckboxItem
@ -231,9 +195,12 @@ const CharacterSearchFilterBar = (props: Props) => {
) )
})} })}
</SearchFilter> </SearchFilter>
)
const elementFilter = (
<SearchFilter <SearchFilter
label={t('filters.labels.element')} label={t('filters.labels.element')}
display="list"
numSelected={ numSelected={
Object.values(elementState) Object.values(elementState)
.map((x) => x.checked) .map((x) => x.checked)
@ -242,9 +209,6 @@ const CharacterSearchFilterBar = (props: Props) => {
open={elementMenu} open={elementMenu}
onOpenChange={elementMenuOpened} onOpenChange={elementMenuOpened}
> >
<DropdownMenu.Label className="Label">
{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
@ -258,7 +222,12 @@ const CharacterSearchFilterBar = (props: Props) => {
) )
})} })}
</SearchFilter> </SearchFilter>
)
return (
<div className={styles.filterBar}>
{rarityFilter}
{elementFilter}
{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);
}
}
@include breakpoint(phone) {
justify-content: center;
}
.label {
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 { .count {
color: var(--text-tertiary); color: var(--text-secondary);
font-weight: $medium; font-weight: $medium;
} }
}
& > .icon { .icon {
$diameter: 16px; $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,10 +72,10 @@ 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)
@ -84,9 +84,6 @@ const SummonSearchFilterBar = (props: Props) => {
open={rarityMenu} open={rarityMenu}
onOpenChange={rarityMenuOpened} onOpenChange={rarityMenuOpened}
> >
<DropdownMenu.Label className="Label">
{t('filters.labels.rarity')}
</DropdownMenu.Label>
{Array.from(Array(rarities.length)).map((x, i) => { {Array.from(Array(rarities.length)).map((x, i) => {
return ( return (
<SearchFilterCheckboxItem <SearchFilterCheckboxItem
@ -100,9 +97,12 @@ const SummonSearchFilterBar = (props: Props) => {
) )
})} })}
</SearchFilter> </SearchFilter>
)
const elementFilter = (
<SearchFilter <SearchFilter
label={t('filters.labels.element')} label={t('filters.labels.element')}
display="list"
numSelected={ numSelected={
Object.values(elementState) Object.values(elementState)
.map((x) => x.checked) .map((x) => x.checked)
@ -111,9 +111,6 @@ const SummonSearchFilterBar = (props: Props) => {
open={elementMenu} open={elementMenu}
onOpenChange={elementMenuOpened} onOpenChange={elementMenuOpened}
> >
<DropdownMenu.Label className="Label">
{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
@ -127,6 +124,12 @@ const SummonSearchFilterBar = (props: Props) => {
) )
})} })}
</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,54 +128,49 @@ 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)
})}
</React.Fragment>
)
}
const renderWeaponSeriesGroup = (index: number) => {
return (
<DropdownMenu.Group className="Group" key={`Group-${index}`}>
{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) => {
return ( return (
<SearchFilterCheckboxItem <SearchFilterCheckboxItem
key={slug} key={x}
onCheckedChange={handleSeriesChange} onCheckedChange={handleProficiencyChange}
checked={seriesState[slug].checked} checked={proficiencyState[x].checked}
valueKey={slug} valueKey={x}
> >
{t(`series.${slug}`)} {t(`proficiencies.${x}`)}
</SearchFilterCheckboxItem> </SearchFilterCheckboxItem>
) )
})}
</>
)
} }
useEffect(() => { const renderWeaponSeries = () => {
sendFilters()
}, [rarityState, elementState, proficiencyState, seriesState])
return ( return (
<div className="SearchFilterBar"> <>
{weaponSeries.map((x, i) => {
return (
<SearchFilterCheckboxItem
key={x.slug}
onCheckedChange={handleSeriesChange}
checked={seriesState[x.slug].checked}
valueKey={x.slug}
>
{t(`series.${x.slug}`)}
</SearchFilterCheckboxItem>
)
})}
</>
)
}
const rarityFilter = (
<SearchFilter <SearchFilter
label={t('filters.labels.rarity')} label={t('filters.labels.rarity')}
key="rarity" key="rarity"
display="list"
numSelected={ numSelected={
Object.values(rarityState) Object.values(rarityState)
.map((x) => x.checked) .map((x) => x.checked)
@ -180,9 +179,6 @@ const WeaponSearchFilterBar = (props: Props) => {
open={rarityMenu} open={rarityMenu}
onOpenChange={rarityMenuOpened} onOpenChange={rarityMenuOpened}
> >
<DropdownMenu.Label className="Label">
{t('filters.labels.rarity')}
</DropdownMenu.Label>
{Array.from(Array(rarities.length)).map((x, i) => { {Array.from(Array(rarities.length)).map((x, i) => {
return ( return (
<SearchFilterCheckboxItem <SearchFilterCheckboxItem
@ -196,10 +192,13 @@ const WeaponSearchFilterBar = (props: Props) => {
) )
})} })}
</SearchFilter> </SearchFilter>
)
const elementFilter = (
<SearchFilter <SearchFilter
label={t('filters.labels.element')} label={t('filters.labels.element')}
key="element" key="element"
display="list"
numSelected={ numSelected={
Object.values(elementState) Object.values(elementState)
.map((x) => x.checked) .map((x) => x.checked)
@ -208,9 +207,6 @@ const WeaponSearchFilterBar = (props: Props) => {
open={elementMenu} open={elementMenu}
onOpenChange={elementMenuOpened} onOpenChange={elementMenuOpened}
> >
<DropdownMenu.Label className="Label">
{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
@ -224,10 +220,13 @@ const WeaponSearchFilterBar = (props: Props) => {
) )
})} })}
</SearchFilter> </SearchFilter>
)
const proficiencyFilter = (
<SearchFilter <SearchFilter
label={t('filters.labels.proficiency')} label={t('filters.labels.proficiency')}
key="proficiency" key="proficiency"
display="grid"
numSelected={ numSelected={
Object.values(proficiencyState) Object.values(proficiencyState)
.map((x) => x.checked) .map((x) => x.checked)
@ -236,52 +235,15 @@ const WeaponSearchFilterBar = (props: Props) => {
open={proficiencyMenu} open={proficiencyMenu}
onOpenChange={proficiencyMenuOpened} onOpenChange={proficiencyMenuOpened}
> >
<DropdownMenu.Label className="Label"> {renderProficiencies()}
{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>
)
const seriesFilter = (
<SearchFilter <SearchFilter
label={t('filters.labels.series')} label={t('filters.labels.series')}
key="series" key="series"
display="grid"
numSelected={ numSelected={
Object.values(seriesState) Object.values(seriesState)
.map((x) => x.checked) .map((x) => x.checked)
@ -290,12 +252,17 @@ const WeaponSearchFilterBar = (props: Props) => {
open={seriesMenu} open={seriesMenu}
onOpenChange={seriesMenuOpened} onOpenChange={seriesMenuOpened}
> >
<DropdownMenu.Label className="Label"> {renderWeaponSeries()}
{t('filters.labels.series')}
</DropdownMenu.Label>
<section>{renderWeaponSeries()}</section>
</SearchFilter> </SearchFilter>
</div> )
return (
<section className={styles.filterBar}>
{rarityFilter}
{elementFilter}
{proficiencyFilter}
{seriesFilter}
</section>
) )
} }