mirror of
https://github.com/Growstuff/growstuff.git
synced 2026-10-30 02:38:47 -04:00
- Introduced `@other_count` in SeedsController to track in-stock and finished seeds based on the filter applied. - Enhanced SeedsHelper with new methods: `seed_owner_label`, `seed_stacks`, and `seed_choice_link` for better organization and readability. - Updated views to utilize new helper methods, improving the display of seed information and filtering options. - Adjusted the Seed model to include methods for determining if a seed's sow-by date has passed and to return the heirloom type. - Improved tests for filters and helper methods to ensure accurate functionality and coverage.
107 lines
4.4 KiB
React
107 lines
4.4 KiB
React
import React, {useEffect, useRef, useState} from 'react';
|
||
|
||
// How long the packets take to fold back into the pile, in ms. Matches the
|
||
// seed-group-close animation in _seeds.scss.
|
||
const CLOSE_MS = 260;
|
||
|
||
const pill = (heirloom) => heirloom && (
|
||
<span className={`seed-pill seed-pill-${heirloom}`}>
|
||
{heirloom.charAt(0).toUpperCase() + heirloom.slice(1)}
|
||
</span>
|
||
);
|
||
|
||
// The owner's avatar, small, beside a pile on a page of other people's seeds.
|
||
const Owner = ({owner}) => owner && (
|
||
<a className="seed-owner" href={owner.path} title={owner.label} aria-label={owner.label}>
|
||
<img src={owner.avatar_url} alt="" width="32" height="32" />
|
||
</a>
|
||
);
|
||
|
||
// Several packets of one variety. Closed, they are a pile with a count on top;
|
||
// opened, the pile spreads out where it stood, one packet each, to pick from.
|
||
// Rails renders the top packet first (see seeds/_stack), so there is something
|
||
// to see before this loads and without JavaScript.
|
||
export default function SeedStack({crop, count, image_url: imageUrl, heirloom, rule, packets, owner}) {
|
||
const [state, setState] = useState('closed');
|
||
const toggle = useRef(null);
|
||
const closeTimer = useRef(null);
|
||
|
||
useEffect(() => () => clearTimeout(closeTimer.current), []);
|
||
|
||
const open = () => setState('open');
|
||
const close = () => {
|
||
setState('closing');
|
||
closeTimer.current = setTimeout(() => {
|
||
setState('closed');
|
||
toggle.current?.focus();
|
||
}, CLOSE_MS);
|
||
};
|
||
|
||
if (state === 'closed') {
|
||
return (
|
||
<div className="seed-stack seed-stack-island">
|
||
<div className="seed-stack-pile">
|
||
<span className="seed-stack-count" aria-hidden="true">×{count}</span>
|
||
<button
|
||
type="button"
|
||
ref={toggle}
|
||
className="seed-packet seed-stack-toggle"
|
||
aria-expanded="false"
|
||
aria-label={`${crop.name}, ${count} packets. Open to see each packet`}
|
||
onClick={open}
|
||
>
|
||
<span className="seed-packet-tear" aria-hidden="true" />
|
||
<img className="seed-packet-window" src={imageUrl} alt="" />
|
||
<span className="seed-packet-name">{crop.name}</span>
|
||
<span className="seed-packet-line seed-stack-hint">{count} packets <i aria-hidden="true">▸</i> open</span>
|
||
{heirloom && <span className="seed-packet-line">{pill(heirloom)}</span>}
|
||
<span className="seed-packet-rule">{rule}</span>
|
||
</button>
|
||
</div>
|
||
<Owner owner={owner} />
|
||
</div>
|
||
);
|
||
}
|
||
|
||
return (
|
||
<div
|
||
className={`seed-group${state === 'closing' ? ' is-closing' : ''}`}
|
||
role="group"
|
||
aria-label={`${crop.name}, ${count} packets`}
|
||
onKeyDown={(event) => { if (event.key === 'Escape' && state === 'open') close(); }}
|
||
>
|
||
<div className="seed-group-head">
|
||
<b>
|
||
{crop.name} · {count} packets
|
||
{owner && <> <span className="seed-group-owner">by <a href={owner.path}>{owner.name}</a></span></>}
|
||
</b>
|
||
<button type="button" className="seed-group-close" aria-expanded="true" onClick={close} disabled={state === 'closing'}>
|
||
Close ▴
|
||
</button>
|
||
</div>
|
||
<div className="seed-group-row">
|
||
{packets.map((packet, index) => (
|
||
<article key={packet.id} className="seed-packet seed-group-packet" style={{'--i': index, '--n': packets.length}}>
|
||
<div className="seed-packet-tear" aria-hidden="true" />
|
||
<a href={packet.path}><img className="seed-packet-window" src={packet.image_url} alt="" /></a>
|
||
<h4 className="seed-packet-name"><a href={packet.path}>{packet.name}</a></h4>
|
||
<p>{packet.when}</p>
|
||
{packet.heirloom && <p>{pill(packet.heirloom)}</p>}
|
||
<p className="seed-packet-detail">{packet.detail}</p>
|
||
{packet.past && <span className="seed-packet-stamp">Past sow-by</span>}
|
||
<div className="seed-packet-rule">{packet.rule}</div>
|
||
<div className="seed-packet-actions">
|
||
<a className="btn btn-primary btn-sm" href={packet.path}>View</a>
|
||
{packet.edit_path && <a className="btn btn-outline-primary btn-sm" href={packet.edit_path}>Edit</a>}
|
||
</div>
|
||
</article>
|
||
))}
|
||
</div>
|
||
<div className="seed-group-foot">
|
||
<a href={crop.path}>Open the {crop.name} page →</a>
|
||
<span className="text-muted"> history, finished packets, plantings these seeds grew</span>
|
||
</div>
|
||
</div>
|
||
);
|
||
}
|