1
0
Fork 0
freesewing/sites/shared/components/designs/design-picker.mjs

176 lines
6.5 KiB
JavaScript
Raw Normal View History

2023-09-24 19:07:16 +02:00
import { designs, tags, techniques } from 'shared/config/designs.mjs'
2023-11-01 13:42:03 +01:00
import { DesignCard, DesignLink, ns as designNs } from 'shared/components/designs/design.mjs'
2023-05-05 19:56:51 +02:00
import { useTranslation } from 'next-i18next'
2023-11-01 13:42:03 +01:00
import { useState } from 'react'
import { FilterIcon, ShowcaseIcon, CisFemaleIcon, ResetIcon } from 'shared/components/icons.mjs'
2023-09-24 19:07:16 +02:00
import { useAtom } from 'jotai'
import { atomWithHash } from 'jotai-location'
import { capitalize, objUpdate } from 'shared/utils.mjs'
2023-09-24 19:07:16 +02:00
import { Difficulty } from 'shared/components/designs/difficulty.mjs'
2023-11-01 13:42:03 +01:00
import { collection } from 'site/hooks/use-design.mjs'
2023-05-01 18:27:06 +02:00
export const ns = designNs
2023-09-24 19:07:16 +02:00
const filterAtom = atomWithHash('filter', { example: true })
export const useFilter = () => {
return useAtom(filterAtom)
}
export const DesignPicker = ({ linkTo = 'new', altLinkTo = 'docs' }) => {
2023-05-05 19:56:51 +02:00
const { t } = useTranslation('designs')
2023-09-24 19:07:16 +02:00
const [filter, setFilter] = useFilter()
2023-11-01 13:42:03 +01:00
const [showFilters, setShowFilters] = useState(false)
2023-05-01 18:27:06 +02:00
2023-05-05 19:56:51 +02:00
// Need to sort designs by their translated title
2023-09-24 19:07:16 +02:00
// let's also apply the filters while we're at it
2023-05-05 19:56:51 +02:00
const translated = {}
2023-11-01 13:42:03 +01:00
for (const d of collection) {
2023-09-24 19:07:16 +02:00
let skip = false
if (
filter.tag &&
filter.tag.filter((tag) => designs[d].tags.includes(tag)).length < filter.tag.length
)
skip = true
if (
filter.tech &&
filter.tech.filter((tech) => designs[d].techniques.includes(tech)).length < filter.tech.length
)
skip = true
if (filter.difficulty && filter.difficulty !== designs[d].difficulty) skip = true
if (!skip) translated[t(`${d}.t`)] = d
}
const updateFilter = (path, val) => {
const newFilter = objUpdate({ ...filter }, path, val)
setFilter(newFilter)
}
const toggle = (type, val) => {
const current = filter[type] || []
const newSet = new Set(current)
if (newSet.has(val)) newSet.delete(val)
else newSet.add(val)
updateFilter(type, [...newSet])
}
2023-05-05 19:56:51 +02:00
return (
2023-09-24 19:07:16 +02:00
<>
<div className="max-w-7xl m-auto">
<div className="flex flex-row flex-wrap gap-1 justify-center font-medium">
{Object.values(translated)
.sort()
.map((d) => (
<DesignLink key={d} linkTo={linkTo} altLinkTo={altLinkTo} name={capitalize(d)} />
))}
</div>
2023-11-01 13:42:03 +01:00
{showFilters ? (
<>
<h6 className="text-center mb-0 mt-4">
Filters ({Object.keys(translated).length}/{Object.keys(designs).length})
</h6>
<div className="flex flex-row gap-1 items-center justify-center flex-wrap my-2">
<b>{t('tags:tags')}:</b>
{tags.map((tag) => (
<button
key={tag}
className={`badge font-medium hover:shadow
${
filter?.tag && Array.isArray(filter.tag) && filter.tag.includes(tag)
? 'badge badge-success hover:badge-error'
: 'badge badge-primary hover:badge-success'
}`}
onClick={() => toggle('tag', tag)}
>
{t(`tags:${tag}`)}
</button>
))}
</div>
<div className="flex flex-row gap-1 items-center justify-center flex-wrap my-4">
<b>{t('techniques:techniques')}:</b>
{techniques.map((tech) => (
<button
key={tech}
className={`badge font-medium hover:shadow
${
filter?.tech && Array.isArray(filter.tech) && filter.tech.includes(tech)
? 'badge badge-success hover:badge-error'
: 'badge badge-accent hover:badge-success'
}`}
onClick={() => toggle('tech', tech)}
>
{t(`techniques:${tech}`)}
</button>
))}
</div>
<div className="flex flex-row gap-2 items-center justify-center flex-wrap my-4">
<b>{t('tags:difficulty')}:</b>
{[1, 2, 3, 4, 5].map((score) => (
<button
onClick={() => updateFilter('difficulty', score)}
key={score}
className={`btn btn-sm ${
filter.difficulty === score ? 'btn-secondary btn-outline' : 'btn-ghost'
}`}
>
<Difficulty score={score} />
</button>
))}
</div>
<div className="flex flex-row gap-4 items-center justify-center flex-wrap my-2">
<button
className="btn btn-secondary btn-outline"
onClick={() => updateFilter('example', !filter.example)}
>
{filter.example ? <CisFemaleIcon /> : <ShowcaseIcon />}
{filter.example ? t('tags:showLineDrawings') : t('tags:showExamples')}
</button>
<button
className="btn btn-secondary btn-outline"
onClick={() => setFilter({ example: 1 })}
>
<ResetIcon />
{t('tags:clearFilter')}
</button>
<button
className="btn btn-secondary btn-outline"
onClick={() => setShowFilters(false)}
>
<FilterIcon />
{t('tags:hideFilters')}
</button>
</div>
</>
) : (
<div className="flex flex-row gap-4 items-center justify-center flex-wrap my-2">
2023-09-24 19:07:16 +02:00
<button
2023-11-01 13:42:03 +01:00
className="btn btn-secondary btn-outline"
onClick={() => updateFilter('example', !filter.example)}
2023-09-24 19:07:16 +02:00
>
2023-11-01 13:42:03 +01:00
{filter.example ? <CisFemaleIcon /> : <ShowcaseIcon />}
{filter.example ? t('tags:showLineDrawings') : t('tags:showExamples')}
2023-09-24 19:07:16 +02:00
</button>
2023-11-01 13:42:03 +01:00
<button className="btn btn-secondary btn-outline" onClick={() => setShowFilters(true)}>
<FilterIcon />
{t('tags:showFilters')}
2023-09-24 19:07:16 +02:00
</button>
2023-11-01 13:42:03 +01:00
</div>
)}
2023-09-24 19:07:16 +02:00
</div>
2023-11-01 13:42:03 +01:00
<div className="grid grid-cols-2 gap-2 mt-4 justify-center sm:grid-cols-3 md:grid-cols-4 lg:grid-cols-5 xl:grid-cols-6 2xl:grid-cols-8 mb-8">
2023-09-24 19:07:16 +02:00
{Object.keys(translated)
.sort()
.map((d) => (
2023-11-01 13:42:03 +01:00
<DesignCard
2023-09-24 19:07:16 +02:00
name={translated[d]}
key={d}
linkTo={linkTo}
altLinkTo={altLinkTo}
2023-09-24 19:07:16 +02:00
lineDrawing={filter.example ? false : true}
/>
))}
</div>
</>
2023-05-05 19:56:51 +02:00
)
2023-05-01 18:27:06 +02:00
}