2025-04-01 16:15:20 +02:00
|
|
|
// Dependencies
|
|
|
|
import {
|
|
|
|
about,
|
|
|
|
collection,
|
|
|
|
tags,
|
|
|
|
techniques,
|
|
|
|
examples,
|
|
|
|
requiredMeasurements,
|
|
|
|
optionalMeasurements,
|
|
|
|
} from '@freesewing/collection'
|
|
|
|
import { capitalize, linkClasses, mutateObject } from '@freesewing/utils'
|
|
|
|
import { measurements as measurementsTranslations } from '@freesewing/i18n'
|
|
|
|
// Hooks
|
2025-05-30 11:29:55 +02:00
|
|
|
import React, { useState, Fragment } from 'react'
|
2025-05-10 10:38:06 +02:00
|
|
|
import { useFilter } from '@freesewing/react/hooks/useFilter'
|
2025-04-01 16:15:20 +02:00
|
|
|
// Components
|
2025-05-30 11:29:55 +02:00
|
|
|
import { Link as WebLink } from '@freesewing/react/components/Link'
|
2025-04-01 16:15:20 +02:00
|
|
|
import {
|
|
|
|
CircleIcon,
|
|
|
|
CisFemaleIcon,
|
|
|
|
DocsIcon,
|
|
|
|
FilterIcon,
|
|
|
|
NewPatternIcon,
|
|
|
|
ResetIcon,
|
|
|
|
ShowcaseIcon,
|
|
|
|
} from '@freesewing/react/components/Icon'
|
|
|
|
import {
|
|
|
|
lineDrawingsFront as lineDrawings,
|
|
|
|
lineDrawingsBack,
|
|
|
|
} from '@freesewing/react/components/LineDrawing'
|
|
|
|
import { IconButton } from '@freesewing/react/components/Button'
|
|
|
|
import { KeyVal } from '@freesewing/react/components/KeyVal'
|
|
|
|
import { MissingLinedrawing } from '../LineDrawing/missing.mjs'
|
|
|
|
|
|
|
|
/**
|
2025-05-10 10:38:06 +02:00
|
|
|
* A component to show the FreeSewing collection and pick a design.
|
2025-04-01 16:15:20 +02:00
|
|
|
*
|
2025-05-10 10:38:06 +02:00
|
|
|
* @component
|
|
|
|
* @param {object} props - All component props
|
|
|
|
* @param {React.Component} [props.Link = false] - A framework specific Link component for client-side routing
|
|
|
|
* @param {boolean} [props.editor = false] - Set this to true when rendering inside the FreeSewing editor
|
|
|
|
* @param {string} [props.linkTo = 'about'] - This controls where to link the design to. One of 'new', 'docs', or 'about'.
|
|
|
|
* @param {functino} [props.onClick = false] - You can pass in an onClick handler rather than using links
|
|
|
|
* @returns {JSX.Element}
|
2025-04-01 16:15:20 +02:00
|
|
|
*/
|
|
|
|
export const Collection = ({ Link = false, linkTo = 'about', editor = false, onClick = false }) => {
|
|
|
|
if (!Link) Link = WebLink
|
|
|
|
|
|
|
|
// State
|
|
|
|
const [filter, setFilter] = useFilter()
|
|
|
|
const [showFilters, setShowFilters] = useState(false)
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Apply filter
|
|
|
|
*/
|
|
|
|
const filtered = {}
|
|
|
|
for (const d of collection) {
|
|
|
|
let skip = false
|
|
|
|
if (
|
|
|
|
filter.tag &&
|
|
|
|
filter.tag.filter((tag) => about[d].tags.includes(tag)).length < filter.tag.length
|
|
|
|
)
|
|
|
|
skip = true
|
|
|
|
if (
|
|
|
|
filter.tech &&
|
|
|
|
filter.tech.filter((tech) => about[d].techniques.includes(tech)).length < filter.tech.length
|
|
|
|
)
|
|
|
|
skip = true
|
|
|
|
if (filter.difficulty && filter.difficulty !== about[d].difficulty) skip = true
|
|
|
|
if (!skip) filtered[d] = d
|
|
|
|
}
|
|
|
|
|
|
|
|
const updateFilter = (path, val) => {
|
|
|
|
// Allow clicking the same difficulty to remove it as a filter
|
|
|
|
if (path === 'difficulty' && val === filter.difficulty) val = 'unset'
|
|
|
|
const newFilter = mutateObject({ ...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])
|
|
|
|
}
|
|
|
|
|
|
|
|
return (
|
|
|
|
<>
|
2025-04-19 13:50:08 +02:00
|
|
|
<div className="tw:max-w-7xl tw:m-auto" data-component="Collection/Collection">
|
2025-04-18 08:07:13 +00:00
|
|
|
<div className="tw:flex tw:flex-row tw:flex-wrap tw:gap-1 tw:justify-center tw:font-medium tw:mb-2">
|
2025-04-01 16:15:20 +02:00
|
|
|
{Object.keys(filtered)
|
|
|
|
.sort()
|
|
|
|
.map((d) =>
|
|
|
|
onClick ? (
|
|
|
|
<button
|
|
|
|
key={d}
|
|
|
|
onClick={() => onClick(d)}
|
2025-04-18 08:07:13 +00:00
|
|
|
className="tw:text-secondary tw:decoration-2 tw:underline tw:capitalize tw:hover:decoration-4 tw:hover:text-secondary tw:bg-transparent tw:border-0 tw:font-medium tw:p-0 tw:text-base tw:hover:cursor-pointer"
|
2025-04-01 16:15:20 +02:00
|
|
|
>
|
|
|
|
{d}
|
|
|
|
</button>
|
|
|
|
) : (
|
|
|
|
<Link
|
|
|
|
key={d}
|
|
|
|
href={linkBuilders[linkTo](d)}
|
2025-04-18 08:07:13 +00:00
|
|
|
className="tw:text-secondary tw:decoration-2 tw:underline tw:capitalize tw:hover:decoration-4 tw:hover:text-secondary"
|
2025-04-01 16:15:20 +02:00
|
|
|
>
|
|
|
|
{d}
|
|
|
|
</Link>
|
|
|
|
)
|
|
|
|
)}
|
|
|
|
</div>
|
|
|
|
{showFilters ? (
|
|
|
|
<>
|
2025-04-18 08:07:13 +00:00
|
|
|
<h6 className="tw:text-center tw:mb-0 tw:mt-4">
|
2025-05-01 10:58:13 -07:00
|
|
|
Filtered Designs ({Object.keys(filtered).length}/{collection.length})
|
2025-04-01 16:15:20 +02:00
|
|
|
</h6>
|
2025-04-18 08:07:13 +00:00
|
|
|
<div className="tw:flex tw:flex-row tw:gap-1 tw:items-center tw:justify-center tw:flex-wrap tw:my-2">
|
2025-04-01 16:15:20 +02:00
|
|
|
<b>Tags:</b>
|
|
|
|
{tags.map((tag) => (
|
|
|
|
<button
|
|
|
|
key={tag}
|
2025-04-18 08:07:13 +00:00
|
|
|
className={`tw:daisy-badge tw:font-medium tw:hover:shadow tw:hover:cursor-pointer
|
2025-04-01 16:15:20 +02:00
|
|
|
${
|
|
|
|
filter?.tag && Array.isArray(filter.tag) && filter.tag.includes(tag)
|
2025-04-18 08:07:13 +00:00
|
|
|
? 'tw:daisy-badge-success hover:tw:daisy-badge-error'
|
|
|
|
: 'tw:daisy-badge-primary hover:tw:daisy-badge-success'
|
2025-04-01 16:15:20 +02:00
|
|
|
}`}
|
|
|
|
onClick={() => toggle('tag', tag)}
|
|
|
|
>
|
|
|
|
{tag}
|
|
|
|
</button>
|
|
|
|
))}
|
|
|
|
</div>
|
2025-04-18 08:07:13 +00:00
|
|
|
<div className="tw:flex tw:flex-row tw:gap-1 tw:items-center tw:justify-center tw:flex-wrap tw:my-4">
|
2025-04-01 16:15:20 +02:00
|
|
|
<b>Techniques</b>
|
|
|
|
{techniques.sort().map((tech) => (
|
|
|
|
<button
|
|
|
|
key={tech}
|
2025-04-18 08:07:13 +00:00
|
|
|
className={`tw:daisy-badge tw:font-medium tw:hover:shadow
|
2025-04-01 16:15:20 +02:00
|
|
|
${
|
|
|
|
filter?.tech && Array.isArray(filter.tech) && filter.tech.includes(tech)
|
2025-04-18 08:07:13 +00:00
|
|
|
? 'tw:daisy-badge tw:daisy-badge-success hover:tw:daisy-badge-error'
|
|
|
|
: 'tw:daisy-badge tw:daisy-badge-accent hover:tw:daisy-badge-success'
|
2025-04-01 16:15:20 +02:00
|
|
|
}`}
|
|
|
|
onClick={() => toggle('tech', tech)}
|
|
|
|
>
|
|
|
|
{tech}
|
|
|
|
</button>
|
|
|
|
))}
|
|
|
|
</div>
|
2025-04-18 08:07:13 +00:00
|
|
|
<div className="tw:flex tw:flex-row tw:gap-2 tw:items-center tw:justify-center tw:flex-wrap tw:my-4">
|
2025-04-01 16:15:20 +02:00
|
|
|
<b>Difficulty:</b>
|
|
|
|
{[1, 2, 3, 4, 5].map((score) => (
|
|
|
|
<button
|
|
|
|
onClick={() => updateFilter('difficulty', score)}
|
|
|
|
key={score}
|
2025-04-18 08:07:13 +00:00
|
|
|
className={`tw:daisy-btn tw:daisy-btn-sm ${
|
2025-04-01 16:15:20 +02:00
|
|
|
filter.difficulty === score
|
2025-04-18 08:07:13 +00:00
|
|
|
? 'tw:daisy-btn-secondary tw:daisy-btn-outline'
|
|
|
|
: 'tw:daisy-btn-ghost'
|
2025-04-01 16:15:20 +02:00
|
|
|
}`}
|
|
|
|
>
|
|
|
|
<Difficulty score={score} />
|
|
|
|
</button>
|
|
|
|
))}
|
|
|
|
</div>
|
2025-04-18 08:07:13 +00:00
|
|
|
<div className="tw:flex tw:flex-row tw:gap-4 tw:items-center tw:justify-center tw:flex-wrap tw:my-2">
|
2025-04-01 16:15:20 +02:00
|
|
|
<button
|
2025-04-18 08:07:13 +00:00
|
|
|
className="tw:daisy-btn tw:daisy-btn-secondary tw:daisy-btn-outline"
|
2025-05-10 10:38:06 +02:00
|
|
|
onClick={() => updateFilter('ld', !filter.ld)}
|
2025-04-01 16:15:20 +02:00
|
|
|
>
|
2025-05-10 10:38:06 +02:00
|
|
|
{filter.ld ? <CisFemaleIcon /> : <ShowcaseIcon />}
|
|
|
|
{filter.ld ? 'Show Examples' : 'Show Line Drawings'}
|
2025-04-01 16:15:20 +02:00
|
|
|
</button>
|
|
|
|
<button
|
2025-04-18 08:07:13 +00:00
|
|
|
className="tw:daisy-btn tw:daisy-btn-secondary tw:daisy-btn-outline"
|
2025-05-10 10:38:06 +02:00
|
|
|
onClick={() => setFilter({ ld: 1 })}
|
2025-04-01 16:15:20 +02:00
|
|
|
>
|
|
|
|
<ResetIcon />
|
|
|
|
Clear Filter
|
|
|
|
</button>
|
|
|
|
<button
|
2025-04-18 08:07:13 +00:00
|
|
|
className="tw:daisy-btn tw:daisy-btn-secondary tw:daisy-btn-outline"
|
2025-04-01 16:15:20 +02:00
|
|
|
onClick={() => setShowFilters(false)}
|
|
|
|
>
|
|
|
|
<FilterIcon />
|
|
|
|
Hide Filters
|
|
|
|
</button>
|
|
|
|
</div>
|
|
|
|
</>
|
|
|
|
) : (
|
2025-04-18 08:07:13 +00:00
|
|
|
<div className="tw:flex tw:flex-row tw:gap-4 tw:items-center tw:justify-center tw:flex-wrap tw:my-2">
|
2025-04-01 16:15:20 +02:00
|
|
|
<button
|
2025-04-18 08:07:13 +00:00
|
|
|
className="tw:daisy-btn tw:daisy-btn-secondary tw:daisy-btn-outline"
|
2025-05-10 10:38:06 +02:00
|
|
|
onClick={() => updateFilter('ld', !filter.ld)}
|
2025-04-01 16:15:20 +02:00
|
|
|
>
|
2025-05-10 10:38:06 +02:00
|
|
|
{filter.ld ? <ShowcaseIcon /> : <CisFemaleIcon />}
|
|
|
|
{filter.ld ? 'Show Examples' : 'Show Line Drawings'}
|
2025-04-01 16:15:20 +02:00
|
|
|
</button>
|
|
|
|
<button
|
2025-04-18 08:07:13 +00:00
|
|
|
className="tw:daisy-btn tw:daisy-btn-secondary tw:daisy-btn-outline"
|
2025-04-01 16:15:20 +02:00
|
|
|
onClick={() => setShowFilters(true)}
|
|
|
|
>
|
|
|
|
<FilterIcon />
|
|
|
|
Show Filters
|
|
|
|
</button>
|
|
|
|
</div>
|
|
|
|
)}
|
|
|
|
</div>
|
|
|
|
<div
|
2025-04-18 08:07:13 +00:00
|
|
|
className={`tw:grid tw:grid-cols-2 tw:gap-2 tw:mt-4 tw:justify-center tw:sm:grid-cols-3 tw:md:grid-cols-4 ${editor ? 'tw:lg:grid-cols-6 tw:2xl:grid-cols-12' : ''} tw:mb-8`}
|
2025-04-01 16:15:20 +02:00
|
|
|
>
|
|
|
|
{Object.keys(filtered)
|
|
|
|
.sort()
|
|
|
|
.map((d) => (
|
|
|
|
<DesignCard
|
|
|
|
name={d}
|
|
|
|
key={d}
|
|
|
|
linkTo={linkTo}
|
|
|
|
onClick={onClick}
|
2025-05-10 10:38:06 +02:00
|
|
|
lineDrawing={filter.ld ? true : false}
|
2025-04-01 16:15:20 +02:00
|
|
|
/>
|
|
|
|
))}
|
|
|
|
</div>
|
|
|
|
</>
|
|
|
|
)
|
|
|
|
}
|
|
|
|
|
|
|
|
const DesignCard = ({ name, lineDrawing = false, linkTo, Link, onClick }) => {
|
|
|
|
if (!Link) Link = WebLink
|
|
|
|
|
2025-05-01 10:41:18 -07:00
|
|
|
const LineDrawing = lineDrawing && lineDrawings[name] ? lineDrawings[name] : MissingLinedrawing
|
2025-04-01 16:15:20 +02:00
|
|
|
const exampleImageUrl = examples.href[name] ? examples.href[name] : noExample
|
|
|
|
const bg = { aspectRatio: '1/1.4' }
|
|
|
|
if (!lineDrawing) {
|
2025-05-20 10:26:58 -07:00
|
|
|
bg.backgroundImage = `url(${exampleImageUrl})`
|
2025-04-01 16:15:20 +02:00
|
|
|
bg.backgroundSize = 'cover'
|
|
|
|
bg.backgroundPosition = 'center center'
|
|
|
|
}
|
|
|
|
|
|
|
|
const inner = (
|
|
|
|
<div
|
2025-05-19 08:20:36 +00:00
|
|
|
className={`tw:flex tw:flex-col tw:flex-nowrap tw:justify-between tw:gap-2 tw:border-neutral-500 tw:group-hover:border-secondary
|
|
|
|
tw:w-full tw:h-full tw:border tw:border-2 tw:border-solid tw:p-0 tw:relative tw:rounded-lg`}
|
2025-04-01 16:15:20 +02:00
|
|
|
style={bg}
|
|
|
|
>
|
|
|
|
<h5
|
2025-05-19 08:20:36 +00:00
|
|
|
className={`tw:text-center tw:py-2 tw:px-4 tw:rounded-t tw:m-0 tw:group-hover:no-underline tw:group-hover:bg-secondary/80 tw:group-hover:text-secondary-content
|
2025-04-19 13:50:08 +02:00
|
|
|
${lineDrawing ? '' : 'tw:bg-neutral/80'}`}
|
2025-04-01 16:15:20 +02:00
|
|
|
>
|
2025-04-19 13:50:08 +02:00
|
|
|
<span className={lineDrawing ? 'tw:text-base-100-content' : 'tw:text-neutral-content'}>
|
|
|
|
{about[name].name}
|
|
|
|
</span>
|
2025-04-01 16:15:20 +02:00
|
|
|
</h5>
|
|
|
|
{lineDrawing ? (
|
2025-05-19 08:20:36 +00:00
|
|
|
<div className="tw:flex-auto tw:flex tw:justify-center">
|
|
|
|
<LineDrawing className="tw:w-5/6 tw:text-base-content" />
|
2025-04-01 16:15:20 +02:00
|
|
|
</div>
|
|
|
|
) : (
|
|
|
|
<span />
|
|
|
|
)}
|
|
|
|
<div
|
2025-05-19 08:20:36 +00:00
|
|
|
className={`tw:flex tw:flex-row tw:items-center tw:justify-center tw:py-1 tw:px-2 tw:rounded-b tw:m-0
|
2025-04-18 08:07:13 +00:00
|
|
|
${lineDrawing ? '' : `tw:text-neutral-content`}`}
|
2025-04-01 16:15:20 +02:00
|
|
|
>
|
2025-04-18 08:07:13 +00:00
|
|
|
<Difficulty score={about[name].difficulty} className="tw:group-hover:text-secondary" />
|
2025-04-01 16:15:20 +02:00
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
)
|
|
|
|
|
|
|
|
return onClick ? (
|
|
|
|
<button
|
|
|
|
onClick={() => onClick(name)}
|
2025-04-18 08:07:13 +00:00
|
|
|
className="tw:hover:bg-secondary/10 tw:rounded-lg tw:group tw:hover:no-underline tw:bg-transparent tw:border-0 tw:hover:cursor-pointer tw:p-0"
|
2025-04-01 16:15:20 +02:00
|
|
|
title={about[name].description}
|
2025-04-19 13:50:08 +02:00
|
|
|
data-component="Collection/DesignCard"
|
2025-04-01 16:15:20 +02:00
|
|
|
>
|
|
|
|
{inner}
|
|
|
|
</button>
|
|
|
|
) : (
|
|
|
|
<Link
|
|
|
|
href={linkBuilders[linkTo](name)}
|
2025-04-18 08:07:13 +00:00
|
|
|
className="tw:hover:bg-secondary/10 tw:rounded-lg tw:group tw:hover:no-underline"
|
2025-04-01 16:15:20 +02:00
|
|
|
title={about[name].description}
|
2025-04-19 13:50:08 +02:00
|
|
|
data-component="Collection/DesignCard"
|
2025-04-01 16:15:20 +02:00
|
|
|
>
|
|
|
|
{inner}
|
|
|
|
</Link>
|
|
|
|
)
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* A helper component to show difficulety of a design
|
|
|
|
*
|
|
|
|
* @param {object} props - All React props
|
|
|
|
* @param {number} props.score - The difficulty score of the design (1-5)
|
|
|
|
*/
|
|
|
|
const Difficulty = ({ score = 0, className = '' }) => (
|
2025-04-18 08:07:13 +00:00
|
|
|
<div className={`tw:flex tw:flex-row tw:items-center ${className}`}>
|
2025-04-01 16:15:20 +02:00
|
|
|
{[0, 1, 2, 3, 4].map((i) => (
|
2025-04-18 08:07:13 +00:00
|
|
|
<CircleIcon key={i} fill={i < score ? true : false} className={`tw:w-4 tw:h-4`} />
|
2025-04-01 16:15:20 +02:00
|
|
|
))}
|
|
|
|
</div>
|
|
|
|
)
|
|
|
|
|
|
|
|
const linkBuilders = {
|
|
|
|
new: (design) =>
|
|
|
|
`/editor/#s={%22design%22%3A%22${design.toLowerCase()}%22%2C%22view%22%3A%22draft%22}`,
|
|
|
|
docs: (design) => `/docs/designs/${design.toLowerCase()}/`,
|
|
|
|
about: (design) => `/designs/${design.toLowerCase()}/`,
|
|
|
|
}
|
|
|
|
|
|
|
|
const noExample =
|
|
|
|
'https://images.pexels.com/photos/5626595/pexels-photo-5626595.jpeg?cs=srgb&fm=jpg&w=640&h=427'
|
|
|
|
|
|
|
|
/**
|
2025-05-10 10:38:06 +02:00
|
|
|
* A component to show info about a FreeSewing design
|
2025-04-01 16:15:20 +02:00
|
|
|
*
|
2025-05-10 10:38:06 +02:00
|
|
|
* @component
|
|
|
|
* @param {object} props - All component props
|
|
|
|
* @param {React.Component} props.Link - A framework specific Link component for client-side routing
|
|
|
|
* @param {string} props.design - The name/id of the design
|
|
|
|
* @param {boolean} props.noDocsLink - Set this to true to not render a link to the documentation
|
|
|
|
* @returns {JSX.Element}
|
2025-04-01 16:15:20 +02:00
|
|
|
*/
|
|
|
|
export const DesignInfo = ({ Link = false, design = false, noDocsLink = false }) => {
|
|
|
|
if (!Link) Link = WebLink
|
|
|
|
|
|
|
|
// State
|
|
|
|
const [back, setBack] = useState(false)
|
|
|
|
|
|
|
|
if (!design) return null
|
|
|
|
|
|
|
|
// Line drawings
|
|
|
|
const LineDrawing = lineDrawings[design] || MissingLinedrawing
|
|
|
|
const LineDrawingBack = lineDrawingsBack[design] || null
|
|
|
|
|
|
|
|
// Make sure these always hold arrays, that way we can just map() over them in the JSX output
|
|
|
|
const codeBy = Array.isArray(about[design].code) ? about[design].code : [about[design].code]
|
|
|
|
const designBy = Array.isArray(about[design].design)
|
|
|
|
? about[design].design
|
|
|
|
: [about[design].design]
|
|
|
|
const tags = about[design].tags || []
|
|
|
|
const techniques = about[design].techniques || []
|
|
|
|
|
|
|
|
const makeButton = (
|
2025-04-18 08:07:13 +00:00
|
|
|
<div className={`tw:grid tw:grid-cols-1 tw:gap-2 tw:mb-4`}>
|
2025-04-01 16:15:20 +02:00
|
|
|
<IconButton href={`/editor/#s={"design"%3A"${design}"%2C"view"%3A"draft"}`} color="primary">
|
2025-04-18 08:07:13 +00:00
|
|
|
<NewPatternIcon className="tw:w-8 tw:h-8" />
|
2025-04-01 16:15:20 +02:00
|
|
|
New {capitalize(design)} pattern
|
|
|
|
</IconButton>
|
|
|
|
</div>
|
|
|
|
)
|
|
|
|
const buttons = noDocsLink ? (
|
|
|
|
makeButton
|
|
|
|
) : (
|
2025-04-18 08:07:13 +00:00
|
|
|
<div className={`tw:grid tw:grid-cols-1 tw:lg:grid-cols-2 tw:gap-2 tw:mb-4`}>
|
2025-04-01 16:15:20 +02:00
|
|
|
<IconButton href={`/docs/designs/${design}`} color="secondary">
|
2025-04-18 08:07:13 +00:00
|
|
|
<DocsIcon className="tw:w-8 tw:h-8" />
|
2025-04-01 16:15:20 +02:00
|
|
|
Documentation
|
|
|
|
</IconButton>
|
|
|
|
{makeButton}
|
|
|
|
</div>
|
|
|
|
)
|
|
|
|
|
|
|
|
return (
|
|
|
|
<>
|
2025-04-18 08:07:13 +00:00
|
|
|
<div className="tw:lg:hidden">{buttons}</div>
|
|
|
|
<div className={`tw:grid tw:grid-cols-1 tw:lg:grid-cols-2 tw:gap-2`}>
|
|
|
|
<div className="tw:relative">
|
|
|
|
<div className="tw tw:top-0 tw:left-0">
|
2025-04-01 16:15:20 +02:00
|
|
|
{back ? <LineDrawingBack /> : <LineDrawing />}
|
|
|
|
</div>
|
|
|
|
{LineDrawingBack ? (
|
|
|
|
<button
|
2025-04-18 08:07:13 +00:00
|
|
|
className="tw:absolute tw:top-2 tw:right-4 tw:start-auto tw:daisy-btn tw:daisy-btn-neutral tw:daisy-btn-outline tw:daisy-btn-xs"
|
2025-04-01 16:15:20 +02:00
|
|
|
onClick={() => setBack(!back)}
|
|
|
|
>
|
|
|
|
{back ? 'Front' : 'Back'} view
|
|
|
|
</button>
|
|
|
|
) : null}
|
|
|
|
</div>
|
|
|
|
<div className="">
|
2025-04-18 08:07:13 +00:00
|
|
|
<div className="tw:mt-2 tw:text-sm tw:opacity-70 tw:font-medium">Description</div>
|
|
|
|
<span className="tw:text-xl">{about[design].description}</span>
|
2025-04-01 16:15:20 +02:00
|
|
|
|
2025-04-18 08:07:13 +00:00
|
|
|
<div className="tw:mt-2 tw:text-sm tw:opacity-70 tw:font-medium">By</div>
|
|
|
|
<div className="tw:flex tw:flex-row tw:flex-wrap tw:gap-1 items-center">
|
2025-04-01 16:15:20 +02:00
|
|
|
{codeBy.map((code) => (
|
|
|
|
<KeyVal key={code} k="code" val={code} color="secondary" />
|
|
|
|
))}
|
|
|
|
{designBy.map((code) => (
|
|
|
|
<KeyVal key={code} k="design" val={code} color="secondary" />
|
|
|
|
))}
|
|
|
|
</div>
|
|
|
|
|
2025-04-18 08:07:13 +00:00
|
|
|
<div className="tw:mt-2 tw:text-sm tw:opacity-70 tw:font-medium">Difficulty</div>
|
2025-04-01 16:15:20 +02:00
|
|
|
<Difficulty score={about[design].difficulty} />
|
|
|
|
|
|
|
|
{optionalMeasurements[design].length > 0 ? (
|
|
|
|
<>
|
2025-04-18 08:07:13 +00:00
|
|
|
<div className="tw:mt-2 tw:text-sm tw:opacity-70 tw:font-medium">
|
2025-04-01 16:15:20 +02:00
|
|
|
Optional Measurements
|
|
|
|
</div>
|
|
|
|
<div className="">
|
|
|
|
{optionalMeasurements[design].map((m, i) => (
|
|
|
|
<Fragment key={m}>
|
|
|
|
<Link
|
|
|
|
href={`/docs/measurements/${m.toLowerCase()}`}
|
|
|
|
key={m}
|
|
|
|
className={linkClasses}
|
|
|
|
>
|
|
|
|
{measurementsTranslations[m]}
|
|
|
|
</Link>
|
|
|
|
{i < optionalMeasurements[design].length - 1 ? <span>, </span> : null}
|
|
|
|
</Fragment>
|
|
|
|
))}
|
|
|
|
</div>
|
|
|
|
</>
|
|
|
|
) : null}
|
|
|
|
|
|
|
|
{requiredMeasurements[design].length > 0 ? (
|
|
|
|
<>
|
2025-04-18 08:07:13 +00:00
|
|
|
<div className="tw:mt-2 tw:text-sm tw:opacity-70 tw:font-medium">
|
2025-04-01 16:15:20 +02:00
|
|
|
Required Measurements
|
|
|
|
</div>
|
|
|
|
<div className="">
|
|
|
|
{requiredMeasurements[design].map((m, i) => (
|
|
|
|
<Fragment key={m}>
|
|
|
|
<Link
|
|
|
|
href={`/docs/measurements/${m.toLowerCase()}`}
|
|
|
|
key={m}
|
|
|
|
className={linkClasses}
|
|
|
|
>
|
|
|
|
{measurementsTranslations[m]}
|
|
|
|
</Link>
|
|
|
|
{i < requiredMeasurements[design].length - 1 ? <span>, </span> : null}
|
|
|
|
</Fragment>
|
|
|
|
))}
|
|
|
|
</div>
|
|
|
|
</>
|
|
|
|
) : null}
|
|
|
|
|
2025-04-18 08:07:13 +00:00
|
|
|
<div className="tw:mt-2 tw:text-sm tw:opacity-70 tw:font-medium">Tags</div>
|
|
|
|
<div className="tw:flex tw:flex-row tw:flex-wrap tw:gap-1 items-center">
|
2025-04-01 16:15:20 +02:00
|
|
|
{tags.map((tag) => (
|
|
|
|
<Link
|
|
|
|
key={tag}
|
2025-04-18 08:07:13 +00:00
|
|
|
className="tw:daisy-badge tw:daisy-badge-primary tw:font-medium tw:hover:shadow tw:hover:cursor-pointer"
|
2025-04-01 16:15:20 +02:00
|
|
|
href={`/designs/#filter={"example"%3Atrue%2C"tag"%3A["${tag}"]}`}
|
|
|
|
>
|
2025-05-10 13:44:03 +02:00
|
|
|
<span className="tw:text-primary-content">{tag}</span>
|
2025-04-01 16:15:20 +02:00
|
|
|
</Link>
|
|
|
|
))}
|
|
|
|
</div>
|
2025-04-18 08:07:13 +00:00
|
|
|
<div className="tw:mt-2 tw:text-sm tw:opacity-70 tw:font-medium">Techniques</div>
|
|
|
|
<div className="tw:flex tw:flex-row tw:flex-wrap tw:gap-1 items-center">
|
2025-04-01 16:15:20 +02:00
|
|
|
{techniques.map((tech) => (
|
|
|
|
<Link
|
|
|
|
key={tech}
|
2025-04-18 08:07:13 +00:00
|
|
|
className="tw:daisy-badge tw:daisy-badge-accent tw:font-medium tw:hover:shadow tw:hover:cursor-pointer"
|
2025-04-01 16:15:20 +02:00
|
|
|
href={`/designs/#filter={"example"%3Atrue%2C"tag"%3A["${tech}"]}`}
|
|
|
|
>
|
2025-05-10 13:44:03 +02:00
|
|
|
<span className="tw:text-accent-content">{tech}</span>
|
2025-04-01 16:15:20 +02:00
|
|
|
</Link>
|
|
|
|
))}
|
|
|
|
</div>
|
|
|
|
|
2025-04-18 08:07:13 +00:00
|
|
|
<div className="tw:mt-2 tw:text-sm tw:opacity-70 tw:font-medium">Examples</div>
|
|
|
|
<div className="tw:flex tw:flex-row tw:flex-wrap tw:gap-1 items-center">
|
2025-04-01 16:15:20 +02:00
|
|
|
<KeyVal
|
|
|
|
k="FreeSewing"
|
|
|
|
val="showcase"
|
|
|
|
color="secondary"
|
2025-04-02 19:02:57 +02:00
|
|
|
href={`/showcase/tags/${design}`}
|
2025-04-01 16:15:20 +02:00
|
|
|
Link={Link}
|
|
|
|
/>
|
|
|
|
<KeyVal
|
|
|
|
k="Instagram"
|
|
|
|
val={`#FreeSewing${capitalize(design)}`}
|
|
|
|
color="secondary"
|
|
|
|
href={`https://www.instagram.com/explore/search/keyword/?q=%23FreeSewing${capitalize(design)}`}
|
|
|
|
/>
|
|
|
|
</div>
|
2025-04-07 07:43:28 +02:00
|
|
|
|
2025-04-18 08:07:13 +00:00
|
|
|
<div className="tw:mt-2 tw:text-sm tw:opacity-70 tw:font-medium">Documentation</div>
|
|
|
|
<div className="tw:flex tw:flex-row tw:flex-wrap tw:gap-1 items-center">
|
2025-04-07 07:43:28 +02:00
|
|
|
<Link href={`/docs/designs/${design}/#notes`}>Designer Notes</Link>,
|
|
|
|
<Link href={`/docs/designs/${design}/#needs`}>What You Need</Link>,
|
|
|
|
<Link href={`/docs/designs/${design}/#fabric`}>Fabric Options</Link>,
|
|
|
|
<Link href={`/docs/designs/${design}/#cutting`}>Cutting Instructions</Link>,
|
|
|
|
<Link href={`/docs/designs/${design}/options/`}>Design Options</Link>,
|
|
|
|
<Link href={`/docs/designs/${design}/instructions/`}>Sewing Instructions</Link>
|
|
|
|
</div>
|
2025-04-18 08:07:13 +00:00
|
|
|
<div className="tw:my-4">{buttons}</div>
|
2025-04-01 16:15:20 +02:00
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
</>
|
|
|
|
)
|
|
|
|
}
|