Adding a page and a menu item
Add a page
1. Create the component src/pages/reports/sales.tsx:
import PageHeader from '@/components/shell/PageHeader';
import { useI18n } from '@/hooks/useI18n';
import { usePageScript } from '@/hooks/usePageScript';
export default function ReportsSalesPage() {
const { t } = useI18n();
usePageScript();
return (
<>
<PageHeader title="reports.sales.title" subtitle="reports.sales.subtitle" section="nav.reports" sectionHref="/reports/sales" />
<div className="fd-page">
<section className="fd-card">
<div className="fd-card__body">
<p data-i18n="reports.sales.empty">{t('reports.sales.empty')}</p>
</div>
</section>
</div>
</>
);
}2. Register the route in src/app/pages.ts:
{ path: '/reports/sales', id: 'reports-sales', title: 'reports.sales.title', layout: 'app', load: () => import('@/pages/reports/sales') },| Field | Meaning |
|---|---|
path | URL of the page (relative to the base path) |
id | Page id: marks the current menu entry (the menu entry with the same id) |
title | Translation key of the document title (… · Orbix) |
layout | app, auth (plus variant) or blank |
attrs | Optional attributes set on <html> while the page is shown |
load | The page component, loaded on demand (its own JavaScript chunk) |
3. Add the texts to every src/locales/<locale>/ folder, for example a new reports.json (see
Languages):
{
"sales": {
"title": "Sales report",
"subtitle": "Revenue by channel and region",
"empty": "No sales in this period yet."
}
}4. Optional page script. Code that works on the rendered markup (third-party widgets, charts, timers) goes in a
function passed to usePageScript(). It runs after render; listeners on document/window added with listen(),
timers added with every() and charts are released when the page unmounts:
// src/scripts/reports.ts
import { listen } from '../core/scope';
export function init(): void {
listen(document, 'keydown', (event) => {
if (event.key === 'r' && event.altKey) document.querySelector<HTMLButtonElement>('[data-report-refresh]')?.click();
});
}import { init } from '@/scripts/reports';
// ...
usePageScript(init);For state that belongs to React (filters, lists, forms), use regular React state and hooks inside the component.
Visit http://localhost:5173/reports/sales.
Pages that need real data fetch it from your API in the component (for example with
useEffector a data library such as TanStack Query) and render it with the same markup.
Add a menu item
The sidebar, the horizontal menu, the two-column rail and the command palette are all built from src/app/menu.ts.
Sections hold items and groups; groups hold children:
{
id: 'reports',
key: 'nav.reports', // section title (translation key)
icon: 'lc-chart-column',
items: [
{ id: 'reports-sales', to: '/reports/sales', key: 'nav.sales_report', icon: 'lc-chart-line' },
{
id: 'reports-more',
key: 'nav.more_reports',
icon: 'lc-folder-open',
children: [{ id: 'reports-stock', to: '/reports/stock', key: 'nav.stock_report' }],
},
],
},| Key | Meaning |
|---|---|
id | Unique navigation id; the page with the same id marks the entry as current and opens its group |
to | Route path of the page |
key | Translation key of the label |
icon | Icon id in the sprite: lc-* (Lucide), tb-* (Tabler), fd-i-* or ox-i-* (Orbix) |
badge | Optional counter: { count: 4, tone: 'primary' } |
Links
Use React Router's <Link to="/reports/sales"> for links between pages. Plain <a href> links created at runtime
(for example inside a data grid cell) are handled by the router too, so the app never reloads.
Icons
Use any icon of the bundled sets with an SVG <use> reference:
<svg className="fd-icon" aria-hidden="true"><use href="#lc-chart-line" /></svg>Browse the sets on the Icons pages of the template. Only the icons you reference are included in the sprite:
after adding a new lc-* or tb-* icon, run npm run assets (it also runs with npm run dev, npm run build
and npm test).