render.jsx

9.7 kB · jsx · 341 lines

1import { Menu as Sitemap, Shell } from 'kit/ui/chrome.jsx';2import { configure } from 'kit/ui/config.js';3import site from '../site.json';4import { cdnUrl, grid, money, ogUrl, tileUrl } from './shop.ts';56configure(site);78const CATEGORIES = [9  ['accessories', 'Accessories'],10  ['bags', 'Bags'],11  ['kids', 'Kids'],12  ['men', 'Men'],13  ['unisex', 'Unisex'],14  ['women', 'Women'],15];1617/* SHELL */1819export function Page({ route, nav, controls, wide, children }) {20  return (21    <Shell route={route} tree={nav} controls={controls} wide={wide}>22      {children}23    </Shell>24  );25}2627/* PIECES */2829function Card({ product, width = 600, eager = false, pick = 0 }) {30  const image = product.images.length ? product.images[pick % product.images.length] : null;31  return (32    <a className="tile" href={`/products/${product.key}/`}>33      {image ? (34        <img35          src={grid(image.url, width)}36          alt={image.alt || product.title}37          width={width}38          height={width}39          loading={eager ? 'eager' : 'lazy'}40          fetchPriority={eager ? 'high' : undefined}41          decoding="async"42        />43      ) : (44        <img src={tileUrl(product.key, 3)} alt={product.title} width="88" height="88" loading="lazy" decoding="async" />45      )}46      <h2>{product.title}</h2>47      <p className="num">{money(product.price)}</p>48    </a>49  );50}5152function Gallery({ products, eager = 0 }) {53  return (54    <div className="gallery square">55      {products.map((product, i) => (56        <Card key={product.key} product={product} eager={i < eager} pick={i} />57      ))}58    </div>59  );60}6162export function Filter({ label, count, next }) {63  return (64    <>65      <a className="filter" href={next}>{`${label} (${count})`}</a>66      <ul className="tabs stack">67        <li><a href="/collections/all/">All</a></li>68        {CATEGORIES.map(([slug, name]) => (69          <li key={slug}><a href={`/collections/${slug}/`}>{name}</a></li>70        ))}71      </ul>72    </>73  );74}7576function Deck({ cards }) {77  return (78    <div className="cards">79      {cards.map((card) => (80        <a className="card" key={card.href} href={card.href}>81          {card.image ? (82            <img src={grid(card.image.url, 600)} alt={card.name} width="600" height="600" loading="lazy" decoding="async" />83          ) : card.key ? (84            <img src={tileUrl(card.key, 3)} alt={card.name} width="88" height="88" loading="lazy" decoding="async" />85          ) : null}86          <p className="code">87            {card.emoji ? <span role="img" aria-label={card.name}>{card.emoji}</span> : null} {card.name}88          </p>89          <p className="name">{`${card.count} design${card.count === 1 ? '' : 's'}`}</p>90        </a>91      ))}92    </div>93  );94}9596/* HOME */9798export function Home({ products, lead }) {99  return (100    <>101      <div className="lede">102        <h1>{site.name}</h1>103        <p className="lead">{lead}</p>104      </div>105      <Gallery products={products} eager={2} />106    </>107  );108}109110/* COLLECTIONS */111112export function Collections({ groups, lead }) {113  return (114    <>115      <div className="lede">116        <h1>Collections</h1>117        <p className="lead">{lead}</p>118      </div>119      {groups.map((group) => (120        <section key={group.name}>121          <h2>{group.name}</h2>122          <Deck cards={group.cards} />123        </section>124      ))}125    </>126  );127}128129export function Collection({ name, products }) {130  return (131    <>132      <div className="lede">133        <h1>{name}</h1>134        <p className="lead">{`${products.length} design${products.length === 1 ? '' : 's'}`}</p>135      </div>136      <Gallery products={products} eager={2} />137    </>138  );139}140141/* PRODUCT */142143export function Product({ product, siblings, collection, files, tiles }) {144  const images = product.images;145  return (146    <>147      <div className="lede">148        <h1>149          <a href={collection}>{product.title}</a>150        </h1>151        <p className="fine">Design {product.key}</p>152      </div>153      <div className="gallery square" data-mockups>154        {images.map((image, i) => (155          <a className="tile" key={image.style} href={grid(image.url, 2000)} data-style={image.style}>156            <img157              src={grid(image.url, 1000)}158              alt={image.alt}159              width="1000"160              height="1000"161              loading={i === 0 ? 'eager' : 'lazy'}162              fetchPriority={i === 0 ? 'high' : undefined}163              decoding="async"164              data-style={image.style}165            />166          </a>167        ))}168      </div>169      <div className="strip" data-strip>170        {tiles.map((n) => (171          <a key={n} href={tileUrl(product.key, n)} download data-tile={n}>172            <img src={tileUrl(product.key, n)} alt={`${product.key} ${n}x${n}`} width="88" height="88" loading="lazy" decoding="async" data-tile={n} />173          </a>174        ))}175      </div>176      <p className="fine" data-downloads>177        <span>Download </span>178        {files.map((name) => (179          <a key={name} href={cdnUrl(product.key, name)} download>{name}</a>180        ))}181        {tiles.map((n) => (182          <a key={`t${n}`} href={tileUrl(product.key, n)} download>{`${n}x${n}`}</a>183        ))}184        <a href={ogUrl(product.key)} download>og</a>185      </p>186      <h2 id="designs">Designs</h2>187      <div className="gallery square" data-siblings>188        {siblings.map((one) => (189          <a190            className="tile"191            key={one.key}192            href={`/products/${one.key}/`}193            aria-current={one.key === product.key ? 'page' : undefined}194            data-key={one.key}195          >196            <img src={tileUrl(one.key, 3)} alt={one.key} width="88" height="88" loading="lazy" decoding="async" />197            <p className="num">{money(one.price)}</p>198          </a>199        ))}200      </div>201    </>202  );203}204205export function Controls({ product, printful, sizes, index, total, buy, expires, live }) {206  return (207    <>208      <p className="num price" data-price>{money(product.price)}</p>209      {sizes.length > 1 && (210        <div className="tabs sizes">211          {sizes.map((size) => (212            <button213              type="button"214              key={size.id}215              data-variant={size.id}216              data-price={size.price}217              data-size={size.size}218              aria-pressed={size.id === product.variant ? 'true' : 'false'}219            >220              {size.size}221            </button>222          ))}223        </div>224      )}225      <button226        type="button"227        className="primary"228        data-add229        data-key={product.key}230        data-title={product.title}231        data-variant={product.variant}232        data-price={product.price}233        data-size={product.variants[0] ? product.variants[0].size : ''}234        disabled={!product.available}235      >236        Add to cart237      </button>238      <a className="buy" href={buy} data-buy rel="noopener">Buy now</a>239      <p className="count" data-expiry={expires} data-live={live} data-ttl></p>240      <div className="row pager">241        <button type="button" data-prev disabled={total < 2}>Prev</button>242        <span className="num" data-index>{`${index + 1} / ${total}`}</span>243        <button type="button" data-next disabled={total < 2}>Next</button>244      </div>245      <button type="button" data-share>Share</button>246      <a className="button" href={printful} rel="noopener">View on Printful</a>247    </>248  );249}250251/* CART */252253export function Cart() {254  return (255    <>256      <div className="lede">257        <h1>Cart</h1>258      </div>259      <div data-cart-lines></div>260      <div className="sum" data-cart-sum></div>261      <template id="line">262        <div className="item">263          <a className="shot" data-href>264            <img alt="" width="88" height="88" decoding="async" />265          </a>266          <div className="who">267            <a data-title></a>268            <p className="dim" data-size></p>269            <div className="qty">270              <button type="button" data-dec aria-label="One less">-</button>271              <b className="num" data-qty></b>272              <button type="button" data-inc aria-label="One more">+</button>273            </div>274          </div>275          <p className="num" data-total></p>276          <button type="button" data-remove>Remove</button>277        </div>278      </template>279    </>280  );281}282283/* PAGES */284285export function Menu({ tree }) {286  return (287    <>288      <div className="lede">289        <h1>Menu</h1>290        <p className="lead">Every page on {site.name}.</p>291      </div>292      <Sitemap tree={tree} />293      <section className="elsewhere">294        <h2>Elsewhere</h2>295        <ul>296          {site.socials.map((one) => (297            <li key={one.href}>298              <a href={one.href}>{one.name}</a>299            </li>300          ))}301          <li>302            <a href={`mailto:${site.contact}`}>{site.contact}</a>303          </li>304        </ul>305      </section>306    </>307  );308}309310export function Doc({ title, lead, body, hero, action }) {311  return (312    <>313      {hero && (314        <figure className="opener">315          <img className="dark" src={`/figures/${hero}-dark.png`} alt="" width="1024" height="1024" decoding="async" />316          <img className="light" src={`/figures/${hero}-light.png`} alt="" width="1024" height="1024" decoding="async" />317        </figure>318      )}319      <div className="lede">320        <h1>{title}</h1>321        {lead && <p className="lead">{lead}</p>}322      </div>323      {action && (324        <div className="prose">325          <p><a className="button primary" href={action.href}>{action.name}</a></p>326        </div>327      )}328      {body && <article className="prose" dangerouslySetInnerHTML={{ __html: body }}></article>}329    </>330  );331}332333export function NotFound() {334  return (335    <div className="lede">336      <h1>Not found</h1>337      <p className="lead">That page is gone, or the design expired.</p>338      <p><a href="/">Back to the shop</a></p>339    </div>340  );341}