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 (
<SearchFilter
label={`${t('filters.labels.proficiency')} ${proficiency}`}
display="grid"
numSelected={numSelected}
open={open}
onOpenChange={onOpenChange}
>
<DropdownMenu.Label className="Label">{`${t(
'filters.labels.proficiency'
)} ${proficiency}`}</DropdownMenu.Label>
<section>
<DropdownMenu.Group className="Group">
{Array.from(Array(proficiencies.length / 2)).map((x, i) => {
const checked =
proficiency == 1
? proficiency1State[proficiencies[i]].checked
: proficiency2State[proficiencies[i]].checked
{Array.from(Array(proficiencies.length)).map((x, i) => {
const checked =
proficiency == 1
? proficiency1State[proficiencies[i]].checked
: proficiency2State[proficiencies[i]].checked
return (
<SearchFilterCheckboxItem
key={proficiencies[i]}
onCheckedChange={onCheckedChange}
checked={checked}
valueKey={proficiencies[i]}
>
{t(`proficiencies.${proficiencies[i]}`)}
</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>
return (
<SearchFilterCheckboxItem
key={proficiencies[i]}
onCheckedChange={onCheckedChange}
checked={checked}
valueKey={proficiencies[i]}
>
{t(`proficiencies.${proficiencies[i]}`)}
</SearchFilterCheckboxItem>
)
})}
</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 (
<div className="SearchFilterBar">
<SearchFilter
label={t('filters.labels.rarity')}
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>
<div className={styles.filterBar}>
{rarityFilter}
{elementFilter}
{renderProficiencyFilter(1)}
{renderProficiencyFilter(2)}
</div>

View file

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

View file

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

View file

@ -1,35 +1,54 @@
button.DropdownLabel {
.pill {
align-items: center;
background: var(--button-contained-bg);
border: none;
border-radius: $unit-2x;
color: var(--text-secondary);
border: 2px solid transparent;
display: flex;
font-size: $font-small;
gap: $unit-half;
flex-direction: row;
justify-content: space-between;
padding: $unit ($unit * 1.5) $unit $unit-2x;
div {
align-items: center;
display: flex;
gap: $unit-half;
}
padding: $unit ($unit * 1.5);
&:hover {
background: var(--button-contained-bg-hover);
color: var(--text-primary);
cursor: pointer;
.text {
color: var(--text-primary);
}
}
.count {
color: var(--text-tertiary);
font-weight: $medium;
@include breakpoint(phone) {
justify-content: center;
}
& > .icon {
$diameter: 16px;
.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 {
color: var(--text-secondary);
font-weight: $medium;
}
}
.icon {
$diameter: $unit-2x;
height: $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);
border: 1px solid rgba(0, 0, 0, 0.1);
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;
flex-direction: column;
gap: $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 {
overflow: hidden;
@ -64,16 +106,4 @@ button.DropdownLabel {
flex-direction: row;
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 classNames from 'classnames'
import * as DropdownMenu from '@radix-ui/react-dropdown-menu'
import ChevronIcon from '~public/icons/Chevron.svg'
@ -8,26 +8,35 @@ import styles from './index.module.scss'
interface Props {
label: string
open: boolean
display: 'grid' | 'list'
numSelected: number
onOpenChange: (open: boolean) => void
children: React.ReactNode
}
const SearchFilter = (props: Props) => {
const displayClasses = classNames({
[styles.grid]: props.display === 'grid',
[styles.list]: props.display === 'list',
})
return (
<DropdownMenu.Root open={props.open} onOpenChange={props.onOpenChange}>
<DropdownMenu.Trigger className="DropdownLabel">
<div>
{props.label}
<span className="count">{props.numSelected}</span>
<DropdownMenu.Trigger className={styles.pill}>
<div className={styles.label}>
<span className={styles.text}>{props.label}</span>
<span className={styles.count}>{props.numSelected}</span>
</div>
<span className="icon">
<span className={styles.icon}>
<ChevronIcon />
</span>
</DropdownMenu.Trigger>
<DropdownMenu.Content className="Dropdown" sideOffset={4}>
{props.children}
<DropdownMenu.Arrow />
<DropdownMenu.Content
className={styles.dropdown}
sideOffset={4}
collisionPadding={16}
>
<div className={displayClasses}>{props.children}</div>
</DropdownMenu.Content>
</DropdownMenu.Root>
)

View file

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

View file

@ -23,12 +23,12 @@ const SearchFilterCheckboxItem = (props: Props) => {
return (
<DropdownMenu.CheckboxItem
className="Item"
className={styles.item}
checked={props.checked || false}
onCheckedChange={handleCheckedChange}
onSelect={(event) => event.preventDefault()}
>
<DropdownMenu.ItemIndicator className="Indicator">
<DropdownMenu.ItemIndicator className={styles.indicator}>
<CheckIcon />
</DropdownMenu.ItemIndicator>
{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()
}, [rarityState, elementState])
return (
<div className="SearchFilterBar">
<SearchFilter
label={t('filters.labels.rarity')}
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>
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>
)
<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>
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 (
<div className={styles.filterBar}>
{rarityFilter}
{elementFilter}
</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
)
useEffect(() => {
sendFilters()
}, [rarityState, elementState, proficiencyState, seriesState])
function rarityMenuOpened(open: boolean) {
if (open) {
setRarityMenu(true)
@ -124,178 +128,141 @@ const WeaponSearchFilterBar = (props: Props) => {
props.sendFilters(filters)
}
const renderWeaponSeries = () => {
const numColumns = 3
const renderProficiencies = () => {
return (
<React.Fragment>
{Array.from({ length: numColumns }, () => 0).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)
<>
{proficiencies.map((x, i) => {
return (
<SearchFilterCheckboxItem
key={x}
onCheckedChange={handleProficiencyChange}
checked={proficiencyState[x].checked}
valueKey={x}
>
{t(`proficiencies.${x}`)}
</SearchFilterCheckboxItem>
)
.map((x, i) => {
return renderSingleWeaponSeries(x.id, x.slug)
})}
</DropdownMenu.Group>
})}
</>
)
}
const renderSingleWeaponSeries = (id: number, slug: string) => {
const renderWeaponSeries = () => {
return (
<SearchFilterCheckboxItem
key={slug}
onCheckedChange={handleSeriesChange}
checked={seriesState[slug].checked}
valueKey={slug}
>
{t(`series.${slug}`)}
</SearchFilterCheckboxItem>
<>
{weaponSeries.map((x, i) => {
return (
<SearchFilterCheckboxItem
key={x.slug}
onCheckedChange={handleSeriesChange}
checked={seriesState[x.slug].checked}
valueKey={x.slug}
>
{t(`series.${x.slug}`)}
</SearchFilterCheckboxItem>
)
})}
</>
)
}
useEffect(() => {
sendFilters()
}, [rarityState, elementState, proficiencyState, seriesState])
const rarityFilter = (
<SearchFilter
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 (
<div className="SearchFilterBar">
<SearchFilter
label={t('filters.labels.rarity')}
key="rarity"
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')}
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>
<section className={styles.filterBar}>
{rarityFilter}
{elementFilter}
{proficiencyFilter}
{seriesFilter}
</section>
)
}