Cart
A basket with quantities and per-line totals. Stock limits are enforced at the stepper, not on submit — letting someone add a ninth of eight units and saying so at checkout is the worst possible order.
src/components/ui/cart.tsxAbout
From the component's own source, where it was written.
A basket: lines, quantities, per-line totals.
Amounts are integer minor units, matching LedgerTable and MoneyInput. Money that has been through a float has already lost — 0.1 + 0.2 is 0.30000000000000004, and a basket is exactly where that surfaces.
Each line shows its unit price *and* its line total once quantity exceeds one. Showing only the total makes it impossible to check, and showing only the unit price makes the sum look wrong.
Quantity controls are stepper buttons rather than a free number field. A cart quantity is realistically 1–10, and a text input invites "12" where "1" and a stray keystroke were meant. Remove is separate from decrement, so reaching zero is never how you delete something.
Stock limits are enforced at the control, not on submit. Letting someone add a ninth of eight units and telling them at checkout is the worst order.
Install
npx astralyx-ui add cart
Usage
import { Cart } from '@/components/ui/cart'
<Cart lines={lines} onQuantityChange={setQuantity} onRemove={remove} />Composer
Change the props and the source below follows.
Aeron chair — remastered
Size B · Graphite
1£1,495.00 each£1,495.00Monitor arm, single
Silver
2£219.00 each£438.00Only 2 in stock.
Desk mat
Charcoal, 90×40
1£45.00 each£45.00
<Cart lines={lines} onQuantityChange={setQuantity} onRemove={remove} />Examples
Live basket
<Cart lines={lines} onQuantityChange={setQuantity} onRemove={remove} />API
Generated from the source, so it cannot fall behind the code. One table per exported component, then hooks, functions and types.
Cart
A basket: lines, quantities, per-line totals.
Extends Omit<ComponentProps<'div'>, 'children'>.
| Prop | Type | Default | Description |
|---|---|---|---|
| lines | CartLine[] | required | { id, name, price, quantity, variant?, image?, max?, note? }. Price is minor units per unit. |
| currency | string | 'USD' | — |
| locale | string | 'en-GB' | — |
| onQuantityChange | (id: string, quantity: number) => void | — | — |
| onRemove | (id: string) => void | — | Separate from decrementing, so reaching zero is never how something gets deleted. |
| empty | ReactNode | — | — |
| removeLabel | string | 'Remove from basket' | — |
| decreaseLabel | (item: string) => string | DEFAULT_DECREASE_LABEL | Accessible names for the quantity stepper, given the line's name. |
| increaseLabel | (item: string) => string | DEFAULT_INCREASE_LABEL | — |
| eachLabel | ReactNode | 'each' | Follows the unit price. |
| stockNote | (max: number) => ReactNode | DEFAULT_STOCK_NOTE | Shown once a line hits its stock cap. |
Types
CartLine
| Field | Type | Description |
|---|---|---|
| id | string | — |
| name | ReactNode | — |
| price | number | Unit price in minor units. |
| quantity | number | Not a free number field. A cart quantity is realistically 1–10, and a text input invites "12" where "1" was meant. |
| image? | ReactNode | Small square image. |
| variant? | ReactNode | — |
| max? | number | Caps the increment control and explains why in words underneath. |
| note? | ReactNode | — |
Behaviour
| Topic | Detail | Description |
|---|---|---|
| unit and line total | both shown | Once quantity exceeds one. Showing only the total makes it uncheckable; showing only the unit price makes the sum look wrong. |