| 1 | "use client"; |
| 2 | |
| 3 | import { useId, useState } from "react"; |
| 4 | import { getMerchSizingCopy, merchSizingGarmentLabel } from "@/lib/content/merch-sizing"; |
| 5 | import { MERCH_SIZE_CHARTS, merchMeasurement, type MerchSizingProductCode, type MerchSizingUnit } from "@/lib/merch/sizing"; |
| 6 | import styles from "./merch-size-guide.module.css"; |
| 7 | |
| 8 | /** Supplier guidance only. This component does not choose a purchase size or enable variants. */ |
| 9 | export function MerchSizeGuide({ locale, initialProductCode = "DJCTX" }: { |
| 10 | locale: string; |
| 11 | initialProductCode?: MerchSizingProductCode; |
| 12 | }) { |
| 13 | const d = getMerchSizingCopy(locale); |
| 14 | const id = useId(); |
| 15 | const [productCode, setProductCode] = useState<MerchSizingProductCode>(initialProductCode); |
| 16 | const [unit, setUnit] = useState<MerchSizingUnit>("cm"); |
| 17 | const chart = MERCH_SIZE_CHARTS.find((item) => item.code === productCode)!; |
| 18 | const garment = merchSizingGarmentLabel(chart.code, locale); |
| 19 | const number = new Intl.NumberFormat(locale, { maximumFractionDigits: 1 }); |
| 20 | const measurement = (value: number | null) => value === null ? "—" : number.format(merchMeasurement(value, unit)); |
| 21 | const caption = d.caption.replace("{garment}", garment).replace("{unit}", d[unit]); |
| 22 | const tolerance = d.tolerance.replace("{value}", measurement(chart.toleranceCm)).replace("{unit}", d[unit]); |
| 23 | const tableNotes = `${id}-bust ${id}-tolerance${chart.warning ? ` ${id}-warning` : ""}`; |
| 24 | |
| 25 | return <section className={styles.guide} aria-labelledby={`${id}-title`}> |
| 26 | <h2 id={`${id}-title`}>{d.title}</h2> |
| 27 | <p>{d.introduction}</p> |
| 28 | <p className={styles.note}>{d.guidance}</p> |
| 29 | <div className={styles.controls}> |
| 30 | <div className={styles.field}> |
| 31 | <label htmlFor={`${id}-garment`}>{d.garment}</label> |
| 32 | <select id={`${id}-garment`} value={productCode} onChange={(event) => { |
| 33 | const selected = MERCH_SIZE_CHARTS.find((item) => item.code === event.target.value); |
| 34 | if (selected) setProductCode(selected.code); |
| 35 | }}> |
| 36 | {MERCH_SIZE_CHARTS.map((item) => <option key={item.code} value={item.code}>{merchSizingGarmentLabel(item.code, locale)}</option>)} |
| 37 | </select> |
| 38 | </div> |
| 39 | <fieldset className={styles.units}> |
| 40 | <legend>{d.units}</legend> |
| 41 | {(["cm", "in"] as const).map((value) => <label key={value}> |
| 42 | <input type="radio" name={`${id}-unit`} value={value} checked={unit === value} onChange={() => setUnit(value)} /> |
| 43 | {d[value]} |
| 44 | </label>)} |
| 45 | </fieldset> |
| 46 | </div> |
| 47 | {unit === "in" ? <p className={styles.note}>{d.conversion}</p> : null} |
| 48 | <div className={styles.tableScroll} role="region" aria-label={d.tableRegion} tabIndex={0}> |
| 49 | <table aria-describedby={tableNotes}> |
| 50 | <caption>{caption}</caption> |
| 51 | <thead><tr> |
| 52 | <th scope="col">{d.size}</th><th scope="col">{d.length}</th><th scope="col">{d.shoulder}</th><th scope="col">{d.bust}</th><th scope="col">{d.sleeve}</th> |
| 53 | </tr></thead> |
| 54 | <tbody>{chart.rows.map((row) => <tr key={row.size} data-confirmation={chart.confirmationNeededSizes.includes(row.size) || undefined}> |
| 55 | <th scope="row">{row.size}{chart.confirmationNeededSizes.includes(row.size) ? <abbr title={d.confirmationRequired}> *</abbr> : null}</th> |
| 56 | <td>{measurement(row.lengthCm)}</td><td>{measurement(row.shoulderCm)}</td><td>{measurement(row.bustCm)}</td><td>{measurement(row.sleeveCm)}</td> |
| 57 | </tr>)}</tbody> |
| 58 | </table> |
| 59 | </div> |
| 60 | <p id={`${id}-bust`} className={styles.note}>{d.bustNote}</p> |
| 61 | <p id={`${id}-tolerance`} className={styles.note}>{tolerance}</p> |
| 62 | {chart.rows.some((row) => row.sleeveCm === null) ? <p className={styles.note}>{d.missingSleeve}</p> : null} |
| 63 | {chart.warning ? <p id={`${id}-warning`} className={styles.warning}>{d[chart.warning]}</p> : null} |
| 64 | <p className={styles.note}><a href={chart.sourceUrl} target="_blank" rel="noopener noreferrer">{d.source} · {chart.code}</a></p> |
| 65 | <p className={styles.note}>{d.dated}</p> |
| 66 | </section>; |
| 67 | } |
| 68 |