Skip to content

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 ​

Xem trước
vue
<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>
tsx
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>
html
<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 ​

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>
ts
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.

tsx
// 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' });
ts
// 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' });
ts
// 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'

PropKiểuMặc định
menu*array—
currentPathstring—
brandstring"thl-ui"
brandHrefstring"/"
userobject—
searchableboolean—
settingsbooleantrue
footerstring | boolean—

Sự kiện: searchuser-select

Slot: brandtopbar-lefttopbar-rightlinksidebar-footerdefaultfooter

SideMenu

<ThlSideMenu> · import { ThlSideMenu } from '@thl-ui/vue'

PropKiểuMặc định
items*array—
currentPathstring—
accordionbooleantrue
horizontalboolean—

Slot: link

TopbarMessages

<ThlTopbarMessages> · import { ThlTopbarMessages } from '@thl-ui/vue'

PropKiểuMặc định
items*array—
iconstring"mail"
titlestring—
footerstring—
footerHrefstring—

SettingsPanel

<ThlSettingsPanel> · import { ThlSettingsPanel } from '@thl-ui/vue'

PropKiểuMặc định
openboolean—

Sự kiện: update:open

Page

<ThlPage> · import { ThlPage } from '@thl-ui/vue'

PropKiểuMặc định
titlestring—
breadcrumbarray—

Slot: actionslinkdefault