AppLayout
AppLayout là toàn bộ khung admin: topbar gradient (tìm kiếm, tin nhắn, thông báo, ngôn ngữ, chế độ màu, menu người dùng), menu bên nhiều cấp có đánh dấu mục đang mở, hồ sơ người dùng, panel cài đặt, tiêu đề trang và footer. Chuyển giữa dọc, mini và ngang bằng config.layout.
Xem trước
<div class="layout-frame"><iframe src="http://localhost:5182/" title="Vue playground" loading="lazy"></iframe><p class="layout-frame__hint">Live Vue playground (run <code>npm run dev:vue</code>). Open the ⚙ settings panel to switch skin, layout, dark mode and language.</p></div>import { AppLayout, Page } from '@thl-ui/react';
import { Link, useLocation } from 'react-router';
<AppLayout menu={menu} currentPath={loc.pathname} user={{ ...user, onSelect: (k) => k === 'logout' && logout() }}
onSearch={(q) => nav(`/search?q=${q}`)}
renderLink={(item, children, className) => <Link to={item.href!} className={className}>{children}</Link>}
topbarRight={<TopbarMessages items={messages} icon="mail" />}
sidebarFooter={<Button variant="success" block>Upgrade</Button>}>
<Page title="Dashboard" breadcrumb={[{ label: 'Home', href: '/' }, { label: 'Dashboard' }]}>…</Page>
</AppLayout><thl-app-layout [menu]="menu" [currentPath]="path()" [user]="user" searchable (search)="search($event)" (userSelect)="onUser($event)">
<ng-template thlMenuLink let-item let-cls="cls" let-content="content">
<a [routerLink]="item.href" [class]="cls"><ng-container [ngTemplateOutlet]="content" /></a>
</ng-template>
<ng-container thlTopbarRight><thl-topbar-messages [items]="messages" icon="mail" /></ng-container>
<div thlSidebarFooter><button thl-button variant="success" block>Upgrade</button></div>
<router-outlet />
</thl-app-layout>Vue
<ThlAppLayout :menu="menu" :current-path="route.path" :user="user" searchable @search="onSearch" @user-select="onUser">
<template #topbar-right><ThlTopbarMessages :items="messages" icon="mail" /></template>
<template #link="{ item, content, class: cls }"><RouterLink :to="item.href" :class="cls"><component :is="() => content" /></RouterLink></template>
<template #sidebar-footer><ThlButton variant="success" block>Nâng cấp</ThlButton></template>
<ThlPage title="Dashboard" :breadcrumb="[{ label: 'Trang chủ', href: '/' }, { label: 'Dashboard' }]">…</ThlPage>
</ThlAppLayout>Menu
import type { MenuItem } from '@thl-ui/core';
export const menu: MenuItem[] = [
{ key: 'dash', label: 'menu.dashboard', icon: 'layout-dashboard', badge: { text: 4, color: 'danger' }, children: [
{ key: 'd1', label: 'menu.dashboard1', href: '/' },
{ key: 'd2', label: 'menu.dashboard2', href: '/dashboard-2' },
] },
{ key: 'h', label: 'menu.apps', heading: true },
{ key: 'inbox', label: 'menu.inbox', icon: 'mail', href: '/inbox' },
];label có thể là khoá i18n (tự dịch) hoặc chuỗi thường. Đường dẫn của mục đang mở được tính từ currentPath.
Layout ngang
Khi đặt layout: 'horizontal', menu chuyển từ cạnh bên lên một thanh nằm dưới topbar (giống Cubic-horizontal). Menu con mở dạng dropdown khi rê chuột hoặc khi focus bằng bàn phím. Menu luôn nằm trên một dòng: mục nào không đủ chỗ sẽ được gom vào dropdown Thêm, và hiện lại khi cửa sổ rộng ra. Dưới 768px, menu trở về sidebar trượt (off-canvas) như bình thường. Cùng một mảng menu dùng được cho mọi layout.
// main.tsx: khởi động ở chế độ ngang
<ThlProvider config={{ layout: 'horizontal' }}>
<AppLayout menu={menu} currentPath={loc.pathname} renderLink={renderLink}>…</AppLayout>
</ThlProvider>
// đổi lúc đang chạy (panel cài đặt ⚙ cũng làm như vậy)
const [, setConfig] = useThlConfig();
setConfig({ layout: 'horizontal' });// main.ts: khởi động ở chế độ ngang
app.use(ThlUI, { layout: 'horizontal' });
// đổi lúc đang chạy (panel cài đặt ⚙ cũng làm như vậy)
const { set } = useThlConfig();
set({ layout: 'horizontal' });// main.ts: khởi động ở chế độ ngang
bootstrapApplication(AppComponent, { providers: [provideThlUI({ layout: 'horizontal' })] });
// đổi lúc đang chạy (panel cài đặt ⚙ cũng làm như vậy)
inject(ThlConfigService).set({ layout: 'horizontal' });Xem thử trong playground: localhost:5182/?layout=horizontal. Playground cũng nhận ?layout=mini, ?theme=red-dark, ?lang=ja và ?mode=dark.
Hành vi responsive
- ≥ 1170px: sidebar đầy đủ (hoặc mini / ngang tuỳ
layout) - 768 – 1169px: tự chuyển sang sidebar mini
- < 768px: sidebar trượt ra từ cạnh (off-canvas) có lớp nền, bật từ topbar
Ngưỡng chuyển đổi đặt bằng components.Sidebar.breakpoint.
API
AppLayout
<ThlAppLayout> · import { ThlAppLayout } from '@thl-ui/vue'
| Prop | Kiểu | Mặc định |
|---|---|---|
menu* | array | — |
currentPath | string | — |
brand | string | "thl-ui" |
brandHref | string | "/" |
user | object | — |
searchable | boolean | — |
settings | boolean | true |
footer | string | boolean | — |
Sự kiện: searchuser-select
Slot: brandtopbar-lefttopbar-rightlinksidebar-footerdefaultfooter
SideMenu
<ThlSideMenu> · import { ThlSideMenu } from '@thl-ui/vue'
| Prop | Kiểu | Mặc định |
|---|---|---|
items* | array | — |
currentPath | string | — |
accordion | boolean | true |
horizontal | boolean | — |
Slot: link
TopbarMessages
<ThlTopbarMessages> · import { ThlTopbarMessages } from '@thl-ui/vue'
| Prop | Kiểu | Mặc định |
|---|---|---|
items* | array | — |
icon | string | "mail" |
title | string | — |
footer | string | — |
footerHref | string | — |
SettingsPanel
<ThlSettingsPanel> · import { ThlSettingsPanel } from '@thl-ui/vue'
| Prop | Kiểu | Mặc định |
|---|---|---|
open | boolean | — |
Sự kiện: update:open
Page
<ThlPage> · import { ThlPage } from '@thl-ui/vue'
| Prop | Kiểu | Mặc định |
|---|---|---|
title | string | — |
breadcrumb | array | — |
Slot: actionslinkdefault