Blocks / Application
Empty project
A first-run screen: a large empty state with one way in, an onboarding checklist and an offer of a call.
Marrow, Wilton Way
Marrow is set up. Now give it something to count.
No stock on the shelves yet
Larder only knows what you tell it you keep. Bring in your stock list and the first full count takes about 40 minutes for a kitchen of 60 covers.
Getting Marrow ready
Add your stock list
Import the spreadsheet you already keep, with names, units and par levels. Larder matches the columns and asks about the ones it cannot place.
Connect your till
Each dish sold takes its ingredients off the shelf, so stock falls during service without anyone counting.
Add your suppliers
Natoora, Neal's Yard Dairy, Flock & Herd: add who you buy from and their cut-off times, and orders build themselves from what is low.
Do your first count
Walk the walk-in, the dry store and the bar with a phone. Every count after this one is shorter.
Invite your team, done
Priya invited Ines, Tom, Kasia, Sam and Leo on Monday.
Install
Writes the block to components/blocks/empty-project.tsx and its .css, and every part it uses to components/equip/, with the parts those are built from.
npx https://equip.studio311.app/equip.tgz add block:empty-projectBuilt from 8 parts
Notes
- The page heading is its own h1, because EmptyState titles go from h2 to h4; the empty state, checklist and help card are each an h2.
- Checklist counts its direct ChecklistItem children for the progress bar, so keep the items as direct children and set done from your own data.
- The glyph is an inline SVG passed to EmptyState's icon, drawn on a 48 grid so its stroke stays light in the 96px tile of size="l".
- It renders on the server; wire the Buttons to your router or upload flow.
Source
components/blocks/empty-project.tsx150 lines
import { Avatar } from "@/components/equip/avatar";
import { Button } from "@/components/equip/button";
import { Card, CardBody } from "@/components/equip/card";
import { Checklist, ChecklistItem } from "@/components/equip/checklist";
import { EmptyState, EmptyStateActions } from "@/components/equip/empty-state";
import { Heading } from "@/components/equip/heading";
import { Icon } from "@/components/equip/icon";
import { Text } from "@/components/equip/text";
import "./empty-project.css";
/* Two bare shelves: one jar and one box drawn in dashes, waiting to be counted. */
function EmptyShelf() {
return (
<svg
className="blk-empty-project-glyph"
viewBox="0 0 48 48"
fill="none"
stroke="currentColor"
strokeWidth="1.5"
strokeLinecap="round"
strokeLinejoin="round"
aria-hidden="true"
>
<path d="M6 19h36" />
<path d="M6 37h36" />
<path d="M10 19v3M38 19v3M10 37v3M38 37v3" />
<path d="M27 19v4.5h8V19" />
<path d="M13 37v-7.5a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2V37" strokeDasharray="2 3" />
<path d="M14 27.5v-2h6v2" strokeDasharray="2 3" />
<path d="M25 37v-9h10v9" strokeDasharray="2 3" />
</svg>
);
}
/*
* The first screen of a new kitchen: nothing counted yet, one clear way in, the five
* things that make Larder useful, and a person to call for anyone who would rather not.
*/
export default function EmptyProject() {
return (
<div className="blk-empty-project">
<div className="blk-empty-project-inner">
<header className="blk-empty-project-top">
<Text size="s" tone="faint">
Marrow, Wilton Way
</Text>
<Heading level={1} className="blk-empty-project-title">
Marrow is set up. Now give it something to count.
</Heading>
</header>
<div className="blk-empty-project-layout">
<div className="blk-empty-project-main">
<div className="blk-empty-project-stage">
<EmptyState
className="blk-empty-project-empty"
size="l"
headingLevel={2}
title="No stock on the shelves yet"
icon={<EmptyShelf />}
>
<Text size="m" tone="quiet">
Larder only knows what you tell it you keep. Bring in your stock list and the first full count takes
about 40 minutes for a kitchen of 60 covers.
</Text>
<EmptyStateActions>
<Button variant="accent" size="l" lead={<Icon name="upload" />}>
Import a stock list
</Button>
<Button variant="outline" size="l">
Add items by hand
</Button>
</EmptyStateActions>
</EmptyState>
</div>
<Checklist title="Getting Marrow ready" className="blk-empty-project-checklist">
<ChecklistItem title="Add your stock list" defaultOpen>
<Text size="s" tone="quiet">
Import the spreadsheet you already keep, with names, units and par levels. Larder matches the columns
and asks about the ones it cannot place.
</Text>
<Button size="s" lead={<Icon name="file-plus" />}>
Import a spreadsheet
</Button>
</ChecklistItem>
<ChecklistItem title="Connect your till">
<Text size="s" tone="quiet">
Each dish sold takes its ingredients off the shelf, so stock falls during service without anyone
counting.
</Text>
<Button size="s" variant="outline" lead={<Icon name="plug" />}>
Choose your till
</Button>
</ChecklistItem>
<ChecklistItem title="Add your suppliers">
<Text size="s" tone="quiet">
Natoora, Neal's Yard Dairy, Flock & Herd: add who you buy from and their cut-off times, and
orders build themselves from what is low.
</Text>
<Button size="s" variant="outline" lead={<Icon name="truck" />}>
Add suppliers
</Button>
</ChecklistItem>
<ChecklistItem title="Do your first count">
<Text size="s" tone="quiet">
Walk the walk-in, the dry store and the bar with a phone. Every count after this one is shorter.
</Text>
<Button size="s" variant="outline" lead={<Icon name="clipboard-check" />}>
Start a count
</Button>
</ChecklistItem>
<ChecklistItem title="Invite your team" done>
<Text size="s" tone="quiet">
Priya invited Ines, Tom, Kasia, Sam and Leo on Monday.
</Text>
<Button size="s" variant="outline" lead={<Icon name="user-plus" />}>
Invite someone else
</Button>
</ChecklistItem>
</Checklist>
</div>
<aside className="blk-empty-project-side" aria-labelledby="empty-project-help-title">
<Card className="blk-empty-project-help">
<CardBody className="blk-empty-project-help-body">
<Avatar name="Rosa Mendes" size="l" alt="" />
<div className="blk-empty-project-help-copy">
<Heading level={2} size="s" id="empty-project-help-title">
Prefer a hand?
</Heading>
<Text size="s" tone="quiet">
Rosa Mendes ran the pass at a 50-cover place in Peckham for nine years. On a 30 minute call she
will set up your stock list with you, from whatever you have.
</Text>
</div>
<Button variant="outline" lead={<Icon name="calendar" />}>
Book a call with Rosa
</Button>
<Text size="s" tone="faint">
Free on every plan. Weekdays, 10:00 to 16:00, between services.
</Text>
</CardBody>
</Card>
</aside>
</div>
</div>
</div>
);
}components/blocks/empty-project.css96 lines
@layer blocks {
.blk-empty-project {
container: blk-empty-project / inline-size;
background: var(--eq-bg);
color: var(--eq-ink);
}
.blk-empty-project-inner {
display: grid;
gap: calc(var(--eq-space-6) * 1.25);
max-width: 76rem;
margin-inline: auto;
padding: calc(var(--eq-space-6) * 1.25) var(--eq-space-4) calc(var(--eq-space-6) * 1.5);
}
.blk-empty-project-top {
display: grid;
gap: var(--eq-space-2);
max-width: 40rem;
}
.blk-empty-project-top > * {
margin: 0;
}
.blk-empty-project-title {
font-size: clamp(1.875rem, 6.5cqi, 3.25rem);
line-height: 1.04;
letter-spacing: -0.03em;
text-wrap: balance;
}
.blk-empty-project-layout {
display: grid;
grid-template-columns: minmax(0, 1fr);
gap: var(--eq-space-5);
}
.blk-empty-project-main {
display: grid;
gap: var(--eq-space-5);
min-width: 0;
}
/* The empty shelf sits on the dotted grid used for product pictures: a space waiting to be filled. */
.blk-empty-project-stage {
border: 1px solid var(--eq-line);
border-radius: var(--eq-radius-l);
background:
radial-gradient(circle at 1px 1px, var(--eq-line-strong) 1px, transparent 0) 0 0 / 18px 18px,
color-mix(in oklab, var(--eq-surface) 70%, var(--eq-bg));
}
.blk-empty-project-checklist {
min-width: 0;
}
.blk-empty-project-side {
min-width: 0;
}
.blk-empty-project-help-body {
display: grid;
justify-items: start;
gap: var(--eq-space-3);
}
.blk-empty-project-help-body > p,
.blk-empty-project-help-copy > * {
margin: 0;
}
.blk-empty-project-help-copy {
display: grid;
gap: var(--eq-space-1);
}
@container blk-empty-project (width >= 40rem) {
.blk-empty-project-inner {
padding-inline: var(--eq-space-6);
}
}
@container blk-empty-project (width >= 60rem) {
.blk-empty-project-layout {
grid-template-columns: minmax(0, 1fr) 18rem;
gap: calc(var(--eq-space-6) * 1.25);
align-items: start;
}
.blk-empty-project-side {
position: sticky;
top: var(--eq-space-4);
}
}
}