Customising
Sooner or later a client wants something Azu doesn’t do. A SKU that’s always upper case, say, or a Print button. You get three levels of control. Start with the smallest one that works.
1. Config
Section titled “1. Config”Labels, icons, groups, page size, and branding live in the resource or in config/azu.ts. No React needed. See Resources and config/azu.ts.
2. Swap a component
Section titled “2. Swap a component”Point any field or column at your own React component. The demo upper-cases its SKUs as you type:
// inertia/admin/components/sku_input.tsxfield.text('sku').component('SkuInput').section('Details'),// inertia/admin/components/sku_input.tsximport { useId } from 'react'import type { FieldProps } from '@mezielabs/azu-react'
export default function SkuInput({ field, value, onChange, errors }: FieldProps<string>) { const id = useId() const errorId = `${id}-error` return ( <div className="azu-field"> <label className="azu-label" htmlFor={id}> {field.label} {field.required && ( <span className="azu-required" aria-hidden> {' '} * </span> )} </label> <input id={id} name={field.name} className="azu-input" value={value ?? ''} onChange={(e) => onChange(e.target.value.toUpperCase())} required={field.required} aria-invalid={errors.length > 0} aria-describedby={errors.length > 0 ? errorId : undefined} /> {errors.length > 0 && ( <span id={errorId} className="azu-field-error"> {errors.join(' ')} </span> )} </div> )}A field component gets FieldProps:
- the
valueandonChange, - the field’s
errors, - the
fielditself: itsname,label, and the validator’srequiredhint, - the form’s other values, as
record.
Note: a field component is responsible for its own label and errors. The demo’s renders both, with Azu’s classes, so it passes the same accessibility checks as Azu’s own controls.
A column works the same way. The demo shows stock as a coloured count:
// inertia/admin/components/stock_bar.tsxcolumn.custom('stock', 'StockBar').label('In stock'),// inertia/admin/components/stock_bar.tsximport type { CellProps } from '@mezielabs/azu-react'
export default function StockBar({ value }: CellProps<number>) { const stock = value ?? 0 return ( <span style={{ color: stock < 10 ? 'var(--azu-danger)' : undefined }}>{stock} in stock</span> )}A cell gets CellProps: its value, the row as record, and the column. .component() works on every column kind. column.custom() is a column with no formatting of its own.
Where components live
Section titled “Where components live”Components in inertia/admin/components are registered by file name, so sku_input.tsx becomes SkuInput.
Each one loads only when a page that uses it opens. So they add nothing to the admin’s initial JavaScript.
To add something to a page without replacing it, name a component for one of a resource’s slots. The demo adds a Print button to orders:
// inertia/admin/components/print_order.tsxslots: { toolbar: 'PrintOrder' },// inertia/admin/components/print_order.tsximport type { SlotProps } from '@mezielabs/azu-react'
export default function PrintOrder({ page }: SlotProps) { // The toolbar slot is on every Orders page; printing is for one order if (page !== 'show') return null
return ( <button type="button" className="azu-button" onClick={() => window.print()}> Print </button> )}Azu’s stylesheet leaves the sidebar, the top bar, and the page’s actions out of print. So the page prints the order alone.
The slots are:
| Slot | Where |
|---|---|
header |
Under the page title, on every page of the resource. |
toolbar |
Beside the page’s own buttons. |
beforeTable |
Above the list. |
afterForm |
Under the create and edit forms. |
A slot component gets SlotProps:
- the
resource, - the
page:index,create,edit, orshow, - the
recordon the edit and detail pages, - the resource’s list URL, as
path.
3. Publish a page
Section titled “3. Publish a page”Copy any of Azu’s pages into your app and edit it however you like:
node ace azu:publish resource/indexThis copies the page to inertia/admin/pages/resource/index.tsx. A comment at the top records the Azu version it came from.
- Page names are snake_case paths, like your app’s own Inertia pages. The older
Resource/Indexspelling works too. - Run
node ace azu:publishwithout a page to list them. - It won’t replace a page you’ve already published unless you pass
--force.
Azu renders your copy from then on. It’s the most flexible option, but you now own that page through upgrades.