0
0
Fork 0
mirror of https://github.com/discourse/discourse.git synced 2026-08-07 13:19:19 +08:00
discourse/.skills/discourse-admin-ui/references/page-shell.md
Martin Brennan 4d7893aa2b
DEV: Add a Discourse admin UI skill (#41040)
- Add a new `discourse-admin-ui` skill distilling
https://meta.discourse.org/t/creating-consistent-admin-interfaces/326780?tl=en
for creating and reviewing consistent Discourse admin interfaces.
- Split guidance into focused references for page shells, content
patterns, filtered settings pages, and plugin admin UIs.
- Include local code anchors so agents can follow current core and
plugin examples without rereading the Meta guide.
2026-06-19 16:19:35 +10:00

153 lines
5.3 KiB
Markdown
Vendored

# Page Shell, Navigation, and Routes
Use this reference when adding or changing a core admin page, page header, breadcrumbs, tabs, or sidebar entry.
## Admin page shape
Most admin config pages sit in this hierarchy:
```text
Admin interface
Config page in sidebar
Optional third-level tabs
New/edit/show routes for records
```
Every sidebar-visible config page needs a stable navigation entry, browser title, page title, and page description. This supports consistency now and admin search/navigation enhancements later.
## Sidebar entries
Add config pages to `ADMIN_NAV_MAP` in `frontend/discourse/app/lib/sidebar/admin-nav-map.js`.
Required or expected keys:
- `name`: unique `snake_case` identifier.
- `route`: Ember route name. Prefer this over `href`.
- `label`: I18n key, usually `admin.config.page_name.title`.
- `description`: I18n key, usually `admin.config.page_name.header_description`.
- `icon`: FontAwesome icon name used in the sidebar.
Use optional keys when relevant:
- `routeModels`: route params in route order.
- `moderator`: `true` if moderators can access the page.
- `keywords`: I18n key containing `|`-separated search synonyms.
- `links`: third-level child routes for admin search; these are not sidebar rows.
- `settings_area` or `settings_category`: for pages backed by filtered site settings.
- `multi_tabbed`: `true` when a page has settings plus other tabs.
Example to inspect: `frontend/discourse/app/lib/sidebar/admin-nav-map.js`, especially `admin_localization`, `admin_login`, and `admin_permalinks`.
## Translations
Core config page title and description keys should be shaped like this:
```yaml
en:
js:
admin:
config:
page_name:
title: "Page title"
header_description: "What admins can manage here."
keywords: "optional|search|terms"
```
Keep UI text sentence-cased unless the string is a table header or a proper name. Do not split translated sentences around links or interpolated values; use placeholders.
## DPageHeader
Use `DPageHeader` from `discourse/ui-kit/d-page-header` for the top of admin pages.
Include:
- `@titleLabel`: translated page title.
- `@descriptionLabel`: translated page description.
- `@learnMoreUrl`: optional docs URL.
- `@hideTabs={{true}}`: when the page has no third-level tabs.
- `:breadcrumbs`: one `DBreadcrumbsItem` for `/admin`, plus the current page and any parent context.
- `:actions`: page-level actions. Use yielded `actions.Primary`, `actions.Default`, `actions.Danger`, or `actions.Wrapped`.
- `:tabs`: third-level `DNavItem` entries.
The component automatically hides itself on admin `new` and `edit` route segments. Override with `@shouldDisplay` only when the route intentionally needs a header.
Current examples:
- Simple page header: `frontend/discourse/admin/templates/admin-config/about.gjs`
- Header around filtered settings: `frontend/discourse/admin/templates/admin-config/localization/settings.gjs`
- Header with actions and tabs: `frontend/discourse/admin/templates/admin/backups.gjs`
- Plugin wrapper header: `frontend/discourse/admin/components/admin-plugin-config-page.gjs`
Minimal shape:
```gjs
import DBreadcrumbsItem from "discourse/ui-kit/d-breadcrumbs-item";
import DPageHeader from "discourse/ui-kit/d-page-header";
import { i18n } from "discourse-i18n";
export default <template>
<DPageHeader
@titleLabel={{i18n "admin.config.example.title"}}
@descriptionLabel={{i18n "admin.config.example.header_description"}}
@hideTabs={{true}}
>
<:breadcrumbs>
<DBreadcrumbsItem @path="/admin" @label={{i18n "admin_title"}} />
<DBreadcrumbsItem
@path="/admin/config/example"
@label={{i18n "admin.config.example.title"}}
/>
</:breadcrumbs>
</DPageHeader>
<div class="admin-config-page__main-area">
{{outlet}}
</div>
</template>
```
## Tabs
Use tabs only for related views inside the same admin context. Do not use them as primary admin navigation; the sidebar owns that.
Tabs are rendered in the `DPageHeader` `:tabs` block with `DNavItem` from `discourse/ui-kit/d-nav-item`.
```gjs
<:tabs>
<DNavItem
@route="admin.example.settings"
@label="settings"
class="admin-example-tabs__settings"
/>
<DNavItem
@route="admin.example.records"
@label="admin.example.records"
class="admin-example-tabs__records"
/>
</:tabs>
```
If plugins should extend the tab list, include the established `PluginOutlet` for that area rather than hardcoding plugin links.
## Browser titles
Admin routes should extend Discourse route classes and implement `titleToken()` when they own a browser title:
```js
import { i18n } from "discourse-i18n";
import DiscourseRoute from "discourse/routes/discourse";
export default class AdminExampleRoute extends DiscourseRoute {
titleToken() {
return i18n("admin.config.example.title");
}
}
```
For filtered settings pages, use the specialized route class described in [filtered-settings-pages.md](filtered-settings-pages.md).
## Breadcrumbs
Breadcrumbs appear above the page header content on normal admin pages. Use a `/admin` crumb first, then page hierarchy crumbs, and mark the current page by using the current path and title. Do not add the normal header/breadcrumb area to third-level new/edit pages; use a back link there instead.
The implementation is via `DBreadcrumbsItem` yielded into the `DPageHeader`; `DPageHeader` provides the container.