// app/screen-library.jsx — browse, search, filter, click into a recipe.
// Responsive: laptop 4-col + feature · iPad 3-col + feature · iPhone 2-col + featured card.
function useLibraryFilter() {
const { allRecipes, state } = useApp();
const q = (state.search || '').trim().toLowerCase();
const f = state.filter || 'all';
const list = allRecipes.filter((r) => {
if (f !== 'all') {
if (!(r.meals || []).includes(f)) return false;
}
if (q) {
const hay = (r.name + ' ' + (r.meals || []).join(' ') + ' ' + r.ingredients.join(' ')).toLowerCase();
if (!hay.includes(q)) return false;
}
return true;
});
return window.LCDomain.sortRecipes(list, state.sort || 'recent');
}
const LIB_CHIPS = ['all', 'breakfast', 'lunch', 'dinner', 'dessert', 'snack'];
// Fixed sort options. Each key has ONE meaningful direction (no asc/desc toggle);
// missing data always sinks to the bottom — see LCDomain.sortRecipes.
const SORT_OPTIONS = [
{ key: 'recent', label: 'Recently cooked', note: 'default order' },
{ key: 'rating-desc', label: 'Rating', note: 'high to low' },
{ key: 'cost-asc', label: 'Cost', note: 'low to high' },
{ key: 'effort-asc', label: 'Effort', note: 'quick first' },
];
function SortControl({ iconOnly }) {
const { state, set } = useApp();
const [open, setOpen] = React.useState(false);
const ref = React.useRef(null);
React.useEffect(() => {
if (!open) return;
const onDown = (e) => { if (ref.current && !ref.current.contains(e.target)) setOpen(false); };
const onKey = (e) => { if (e.key === 'Escape') setOpen(false); };
document.addEventListener('mousedown', onDown);
document.addEventListener('keydown', onKey);
return () => { document.removeEventListener('mousedown', onDown); document.removeEventListener('keydown', onKey); };
}, [open]);
const cur = state.sort || 'recent';
const active = cur !== 'recent';
const curOpt = SORT_OPTIONS.find((o) => o.key === cur) || SORT_OPTIONS[0];
return (
{open && (
Sort by
{SORT_OPTIONS.map((o) => {
const on = o.key === cur;
return (
);
})}
)}
);
}
function ClickableCard({ r, size }) {
const { openRecipe, photoFor } = useApp();
return (
openRecipe(r.id)} style={{ cursor: 'pointer' }}>
);
}
function SearchBar({ wide }) {
const { state, set, allRecipes } = useApp();
return (
set({ search: e.target.value })}
placeholder="search recipes, ingredients"
style={{ border: 'none', background: 'transparent', outline: 'none', flex: 1,
fontFamily: 'Bricolage Grotesque', fontSize: 15, color: 'var(--ink)' }} />
{state.search ? (
) : (
{allRecipes.length}
)}
);
}
function ChipRow({ scroll }) {
const { state, set } = useApp();
return (
{LIB_CHIPS.map((c) => {
const active = state.filter === c;
return (
);
})}
);
}
function EmptyResults() {
const { set } = useApp();
return (
Nothing matches that.
);
}
// ── Desktop / tablet ──
function LibraryWide({ compact }) {
const { allRecipes, openRecipe, photoFor, go, state } = useApp();
const list = useLibraryFilter();
const featured = allRecipes.find((r) => r.id === 'sichuan-chicken');
const cols = compact ? 3 : 4;
const pad = compact ? '0 32px' : '0 48px';
const searching = state.search || state.filter !== 'all';
return (
{/* Title row */}
Your library · recently cooked
{allRecipes.length} recipes
you cook.
go('edit', { isNew: true })}>
New recipe
{/* Search + filters + sort */}
{/* Feature (only when not searching) */}
{!searching && featured && (
openRecipe(featured.id)} style={{ cursor: 'pointer',
padding: compact ? '36px 32px 32px' : '26px 48px 32px', display: 'grid',
gridTemplateColumns: '0.95fr 1.05fr', gap: compact ? 40 : 56, alignItems: 'stretch' }}>
this week's pick
{featured.description}
{window.LCDomain.costPerServeLabel(featured) ? `${window.LCDomain.costPerServeLabel(featured)} /serve` : 'no cost yet'} · 35 min · serves 4
View →
)}
{/* Grid header */}
{searching ? `${list.length} ${list.length === 1 ? 'match' : 'matches'}` : 'The full library'}
{/* Grid */}
{list.length === 0 ?
: (
{list.map((r) => )}
)}
);
}
// ── Mobile ──
function LibraryMobile() {
const { allRecipes, openRecipe, photoFor, state } = useApp();
const list = useLibraryFilter();
const featured = allRecipes.find((r) => r.id === 'sichuan-chicken');
const searching = state.search || state.filter !== 'all';
return (
Your library
{allRecipes.length} recipes
you cook.
{!searching && featured && (
openRecipe(featured.id)} style={{ position: 'relative',
aspectRatio: '4 / 5', borderRadius: 18, overflow: 'hidden', cursor: 'pointer',
boxShadow: '0 20px 50px rgba(0,0,0,0.5)' }}>
★ THIS WEEK
Mains · Asian · Quick
sichuan-style cold chicken
with chilli oil.
{window.LCDomain.costPerServeLabel(featured) || '$4.20'} /serve · 35 min
)}
{searching ? `${list.length} ${list.length === 1 ? 'match' : 'matches'}` : 'Full library'}
{list.length === 0 ?
: (
{list.map((r) => )}
)}
);
}
function LibraryScreen() {
const { layout } = useApp();
return (
{layout === 'mobile' ? : }
);
}
Object.assign(window, { LibraryScreen });