mirror of
https://github.com/Growstuff/growstuff.git
synced 2026-09-30 09:54:28 -04:00
Make the crop chip the planting's menu, for annuals and perennials
Clicking a chip opens the planting's menu under it (view, edit, add photo, record harvest, save seeds, mark as finished), in place of the three dots on annual rows, so perennials get the same menu. A caret shows the chip opens something. Someone who can't change the planting still gets a plain link chip. The menu items are lowercase for every planting menu. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
1 parent
6987d66f95
commit
eb18622b78
7 files changed
+102
-48
No files matched your search
@@ -16,7 +16,7 @@ function handled(event, buttonId) {
|
||||
if (dropdown) dropdown.hide();
|
||||
}
|
||||
|
||||
export default function ActionsMenu({id, actions, label = 'Actions', ariaLabel, className = 'btn dropdown-toggle', onSelect}) {
|
||||
export default function ActionsMenu({id, actions, label = 'Actions', ariaLabel, className = 'btn dropdown-toggle', menuClassName = 'dropdown-menu-end', onSelect}) {
|
||||
if (!actions || actions.length === 0) return null;
|
||||
|
||||
const buttonId = `actions-${id}`;
|
||||
@@ -35,7 +35,7 @@ export default function ActionsMenu({id, actions, label = 'Actions', ariaLabel,
|
||||
>
|
||||
{label}
|
||||
</a>
|
||||
<div className="dropdown-menu dropdown-menu-end" aria-labelledby={buttonId}>
|
||||
<div className={`dropdown-menu ${menuClassName}`} aria-labelledby={buttonId}>
|
||||
{actions.map((action) => (
|
||||
<React.Fragment key={action.key}>
|
||||
{action.divider && <div className="dropdown-divider" />}
|
||||
|
||||
@@ -2,7 +2,7 @@ import React from 'react';
|
||||
|
||||
import {isPlainClick} from '../events';
|
||||
import ActionsMenu from './ActionsMenu';
|
||||
import CropChip from './CropChip';
|
||||
import PlantingChip from './PlantingChip';
|
||||
import PlantingRow from './PlantingRow';
|
||||
|
||||
// One garden: its name and actions menu (top right), a picture, then what is
|
||||
@@ -11,6 +11,14 @@ import PlantingRow from './PlantingRow';
|
||||
export default function GardenCard({garden, defaultIconUrl, onPlant, highlightedId, highlightKind, onPlantingUpdated, onEditPlanting, onHarvestPlanting, onSaveSeedsPlanting, onAddPhoto, onFinishPlanting}) {
|
||||
const {id, name, url, image_url: imageUrl, owner, actions, plant_url: plantUrl, perennials, annuals} = garden;
|
||||
const empty = perennials.length === 0 && annuals.length === 0;
|
||||
// What each planting's menu items open, for annuals and perennials alike.
|
||||
const handlers = {
|
||||
edit: onEditPlanting,
|
||||
harvest: onHarvestPlanting,
|
||||
seeds: onSaveSeedsPlanting,
|
||||
finish: onFinishPlanting,
|
||||
photo: onAddPhoto && ((planting, action) => onAddPhoto({label: planting.crop.name, href: action.href})),
|
||||
};
|
||||
function plant(event) {
|
||||
if (onPlant && isPlainClick(event)) {
|
||||
event.preventDefault();
|
||||
@@ -59,12 +67,12 @@ export default function GardenCard({garden, defaultIconUrl, onPlant, highlighted
|
||||
{perennials.length > 0 ? (
|
||||
<div className="garden-card-chips">
|
||||
{perennials.map((planting) => (
|
||||
<CropChip
|
||||
<PlantingChip
|
||||
key={planting.id}
|
||||
url={planting.url}
|
||||
crop={planting.crop}
|
||||
planting={planting}
|
||||
defaultIconUrl={defaultIconUrl}
|
||||
highlighted={planting.id === highlightedId}
|
||||
handlers={handlers}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
@@ -84,11 +92,7 @@ export default function GardenCard({garden, defaultIconUrl, onPlant, highlighted
|
||||
highlighted={planting.id === highlightedId}
|
||||
highlightKind={highlightKind}
|
||||
onUpdated={onPlantingUpdated}
|
||||
onEdit={onEditPlanting}
|
||||
onHarvest={onHarvestPlanting}
|
||||
onSaveSeeds={onSaveSeedsPlanting}
|
||||
onFinish={onFinishPlanting}
|
||||
onAddPhoto={onAddPhoto && ((planting, action) => onAddPhoto({label: planting.crop.name, href: action.href}))}
|
||||
handlers={handlers}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
import React from 'react';
|
||||
|
||||
import {selectPlantingAction} from '../plantingActions';
|
||||
import ActionsMenu from './ActionsMenu';
|
||||
import CropChip from './CropChip';
|
||||
|
||||
// A planting's crop chip, which is also its menu: click it for view, edit,
|
||||
// harvest and the rest, the same for annuals and perennials. Someone who can't
|
||||
// change the planting has no menu, so for them it stays a plain link to it.
|
||||
export default function PlantingChip({planting, defaultIconUrl, highlighted, handlers}) {
|
||||
const {id, url, crop, actions} = planting;
|
||||
|
||||
if (!actions || actions.length === 0) {
|
||||
return <CropChip url={url} crop={crop} defaultIconUrl={defaultIconUrl} highlighted={highlighted} />;
|
||||
}
|
||||
|
||||
return (
|
||||
<ActionsMenu
|
||||
id={`planting-${id}`}
|
||||
actions={actions}
|
||||
label={(
|
||||
<>
|
||||
<img className="crop-icon" src={crop.icon_url || defaultIconUrl} alt="" />
|
||||
{crop.name}
|
||||
</>
|
||||
)}
|
||||
ariaLabel={`Actions for ${crop.name}`}
|
||||
className={`chip crop-chip chip-menu-toggle dropdown-toggle${highlighted ? ' planting-just-added' : ''}`}
|
||||
menuClassName="dropdown-menu-start planting-menu"
|
||||
onSelect={selectPlantingAction(planting, handlers)}
|
||||
/>
|
||||
);
|
||||
}
|
||||
@@ -1,22 +1,21 @@
|
||||
import React from 'react';
|
||||
|
||||
import {formatDate} from '../dates';
|
||||
import {isPlainClick} from '../events';
|
||||
import ActionsMenu from './ActionsMenu';
|
||||
import CropChip from './CropChip';
|
||||
import PlantedDate from './PlantedDate';
|
||||
import PlantingChip from './PlantingChip';
|
||||
|
||||
// One annual planting as a row of three aligned columns: the crop (and when it
|
||||
// was planted), how it is getting on (badges, then a progress bar or a note
|
||||
// when there is nothing to predict from), and its own actions menu.
|
||||
export default function PlantingRow({planting, defaultIconUrl, highlighted, highlightKind = 'added', onUpdated, onEdit, onHarvest, onSaveSeeds, onAddPhoto, onFinish}) {
|
||||
const {id, url, crop, badges} = planting;
|
||||
// One annual planting as a row of two aligned columns: the crop, which is also
|
||||
// the planting's menu (and when it was planted), and how it is getting on
|
||||
// (badges, then a progress bar or a note when there is nothing to predict from).
|
||||
// `handlers` is what the menu's items open (see selectPlantingAction).
|
||||
export default function PlantingRow({planting, defaultIconUrl, highlighted, highlightKind = 'added', onUpdated, handlers}) {
|
||||
const {badges} = planting;
|
||||
|
||||
return (
|
||||
<div className={`planting-row${highlighted ? ` planting-just-${highlightKind}` : ''}`}>
|
||||
<div className="planting-row-crop">
|
||||
<div className="planting-row-chip">
|
||||
<CropChip url={url} crop={crop} defaultIconUrl={defaultIconUrl} highlighted={highlighted && highlightKind === 'added'} />
|
||||
<PlantingChip planting={planting} defaultIconUrl={defaultIconUrl} highlighted={highlighted && highlightKind === 'added'} handlers={handlers} />
|
||||
{highlighted && highlightKind === 'harvested' && (
|
||||
<span className="harvest-recorded-tag"><i className="fa fa-check" aria-hidden="true" /> Harvest recorded</span>
|
||||
)}
|
||||
@@ -39,22 +38,6 @@ export default function PlantingRow({planting, defaultIconUrl, highlighted, high
|
||||
<Progress percentage={planting.percentage_grown} state={planting.progress_state} finishLabel={planting.finish_predicted_label} harvests={planting.harvests} />
|
||||
)}
|
||||
</div>
|
||||
<div className="planting-row-actions">
|
||||
<ActionsMenu
|
||||
id={`planting-${id}`}
|
||||
actions={planting.actions}
|
||||
label={<i className="fa fa-ellipsis-v" aria-hidden="true" />}
|
||||
ariaLabel={`Actions for ${crop.name}`}
|
||||
className="btn btn-sm btn-link actions-toggle-dots"
|
||||
onSelect={(action, event) => {
|
||||
const open = {edit: onEdit, harvest: onHarvest, seeds: onSaveSeeds, photo: onAddPhoto, finish: onFinish}[action.key];
|
||||
if (open && isPlainClick(event)) {
|
||||
event.preventDefault();
|
||||
open(planting, action);
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
import {isPlainClick} from './events';
|
||||
|
||||
// What a click on one of a planting's menu items does: if there is a dialog for
|
||||
// that item (handlers is keyed by the action's key: edit, harvest, seeds, photo,
|
||||
// finish) it opens it, over the list; anything else, and ctrl/cmd-click, follows
|
||||
// the link as usual.
|
||||
export function selectPlantingAction(planting, handlers) {
|
||||
return (action, event) => {
|
||||
const open = handlers[action.key];
|
||||
if (open && isPlainClick(event)) {
|
||||
event.preventDefault();
|
||||
open(planting, action);
|
||||
}
|
||||
};
|
||||
}
|
||||
Reference in new issue
Block a user