6.2 KiB
CLAUDE.md — src/components/nearle_components/
Rules for editing the shared NearlExpress UI primitives.
These components are imported across every page. Changes here have fan-out impact — measure twice, cut once.
1. What lives here
| Component | Use | Status |
|---|---|---|
LocationAutocomplete.js |
Zone picker on every operator page | ✅ Canonical — has pill variant matching DT system |
DebounceSearchBar.js |
500ms-debounced search with ⌘/Ctrl+K focus | ✅ Canonical |
LoaderWithImage.js |
Inline branded spinner for "loading more" rows | ✅ Canonical |
GlobalToast.js |
Global toast wrapper | ✅ Used at root |
SearchBar.js |
Non-debounced search input | Legacy — prefer DebounceSearchBar |
TableLoader.js |
Inline table loading state | Legacy — prefer OrdersTableSkeleton |
TitleCard.js |
Old page header | ⛔ Legacy — do not use on new pages. The replacement is the gradient <Paper> header pattern documented in root CLAUDE.md §6, implemented in deliveries.js (search for the comment Header near the first <Paper> in the JSX return). |
2. Design system token discipline
The DT design tokens (palette, alpha helpers, pillFieldSx, SoftPaper, AccentAvatar) are documented in the root CLAUDE.md §6 and the source-of-truth implementation is the token block near the top of src/pages/nearle/deliveries/deliveries.js (search for const DT = {).
Hard rules when editing components here:
- Universal brand colour is
#662582(NearlExpress purple — fromthemes/theme/default.jsprimary.main). Every brand surface (page header, dialog header, KPI primary tile, search bars, edit-action buttons, scrollbars) uses this. Gradient pair:#662582 → #9255AB. - Semantic status palette is distinct from the brand. Use these for lifecycle indicators only: sky
#0ea5e9, emerald#10b981, amber#f59e0b, red#ef4444, status-purple#8b5cf6(lighter than brand), cyan#06b6d4, teal#14b8a6, orange#f97316, muted#94a3b8, indigo#6366f1(Accepted status). Don't replace these with brand purple — operators colour-code on them. - Don't introduce a colour from
theme.palettefor new surfaces — those are Mantis defaults and don't match the DT system. Use the hex values above directly. - Border radii:
12(inner),16(card),999(pill). No other values. - Shadows:
DT.shadowSoft/DT.shadowMd/DT.shadowPop. No rawbox-shadowstrings.
Some existing redesigned pages (
deliveries.js,Tenants.js,clientPricing.js) still use#6366f1as the brand accent — this is a legacy from the first design pass.customers.jsand thecreateorder1Saved-Address dialog are already on#662582. When you next edit one of the legacy pages, migrate it to brand purple in the same PR.
3. LocationAutocomplete — the pill prop is opt-in
The component supports two visual modes, controlled by the pill prop:
// Default (legacy, "Select Zones" label, outlined TextField) — used by old pages
<LocationAutocomplete setAppId={...} setLocoName={...} />
// Pill variant — used by all redesigned pages (deliveries, tenants, pricing, customers)
<LocationAutocomplete
pill
accentColor="#6366f1"
icon={<MdMyLocation size={14} />}
placeholder="Select Zone"
paperComponent={SoftPaper}
setAppId={...}
setLocoName={...}
/>
- For any new page: use
pill. Always. - For existing legacy pages (orders, invoice, riders, etc.): keep the default until that page is redesigned. Don't change them piecemeal.
- The
accentColordefaults to#6366f1— only override when the page's accent is different (rare).
4. DebounceSearchBar — debounce + ⌘/Ctrl+K shortcut
<DebounceSearchBar
value={searchword} // controlled input value
onChange={setSearchword} // fires on every keystroke
onDebouncedChange={setDebouncedSearch} // fires 500ms after the last keystroke
placeholder="Search (ctrl+k)"
sx={{ ... }} // pill styling lives in the call site, not here
/>
- Don't lower the debounce below 500ms. TanStack Query's cache keys include
debouncedSearch, so each keystroke would cause a new server hit if the debounce drops. - ⌘/Ctrl+K to focus and
Escto blur are wired globally inside this component. Don't add competing keyboard shortcuts at the page level using the same keys. - The pill aesthetic (rounded
999, tinted bg, accent border) is applied via thesxprop at the call site — see the<DebounceSearchBarusage inside the "Status Tabs + Search" section ofdeliveries.jsfor the canonical incantation.
5. When to add a NEW shared component here
Only when:
- Used by two or more pages in production code.
- Has its own internal state or shortcuts (otherwise just use an
AccentAvatar+Boxinline). - The component encapsulates a non-trivial pattern that's been duplicated more than twice.
Don't add a wrapper that just renames an MUI primitive (e.g. <NearleButton>). Don't add a component that's a single instance of a styled Paper.
6. When to inline instead
The DT token block at the top of each page is repeated intentionally. Don't extract it into a shared module here. Reasons:
- It's a 30-line block of constants — repetition is fine.
- Pages occasionally tweak a token for their own use (e.g. picking different KPI colours). A shared module would lock everyone into the same defaults.
- Importing from a shared file creates a cross-page coupling that's painful when one page wants to evolve its visual language.
Same logic for SoftPaper, AccentAvatar, pillFieldSx — these are 5–20 line helpers that live inside each page file. Don't extract.
7. Backwards compatibility
If you change a component's public prop signature:
- Old call sites still using the old prop name will break silently (React doesn't warn on unknown props).
- Either: keep the old prop name working (alias both), OR grep
src/pages/for all consumers and update them in the same PR.
The component's forwardRef shape is part of the contract — Tenants.js uses tenantRef / locationRef to imperatively focus inputs. Don't break ref-forwarding.