npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@aseansc-admin/ui

v2.10.0

Published

ASC Internal UI Component Library — Angular 20 + PrimeNG 20 + TailwindCSS 4

Readme

@aseansc-admin/ui

ASC Internal UI Component Library — Angular 20 · PrimeNG 20 · TailwindCSS 4


Migration — v0.5.x → v0.6.0

Breaking: Tailwind spacing scale — base unit đổi từ 0.25rem sang 1px. Tất cả numeric spacing class cần nhân ×4:

p-4  → p-16    gap-2 → gap-8    px-6 → px-24
w-12 → w-48    h-6   → h-24     mt-1 → mt-4

Chạy lệnh migrate tự động trong project:

# Dùng Perl (Linux/macOS) — backup trước khi chạy
find src \( -name "*.ts" -o -name "*.html" \) | xargs perl -pi -e \
  's/\b(p|px|py|pt|pb|pl|pr|m|mx|my|mt|mb|ml|mr|gap|w|h)-([1-9][0-9]*)\b/$1."-".($2*4)/ge'

Hoặc xem CHANGELOG để biết chi tiết.


Installation

npm install @aseansc-admin/ui

Peer dependencies

npm install @angular/core@^20 @angular/cdk@^20

Setup

1. app.config.ts

import { ApplicationConfig, provideZoneChangeDetection,
         provideBrowserGlobalErrorListeners }  from '@angular/core';
import { provideRouter }                        from '@angular/router';
import { provideHttpClient }                    from '@angular/common/http';
import { provideAnimations }                    from '@angular/platform-browser/animations';
import { provideAscUI }                         from '@aseansc-admin/ui';
import { appRoutes }                            from './app.routes';

export const appConfig: ApplicationConfig = {
  providers: [
    provideBrowserGlobalErrorListeners(),
    provideAscUI({
      theme:  { darkMode: false },
      locale: 'vi-VN',           // 'vi-VN' | 'en-US'
      table:  { rows: 20 },
    }),
    provideAnimations(),
    provideZoneChangeDetection({ eventCoalescing: true }),
    provideRouter(appRoutes),
    provideHttpClient(),
  ],
};

2. styles.scss

@import "@aseansc-admin/ui/preset.css";
@use 'prismjs/themes/prism-okaidia.css';

Lưu ý: dòng import preset phải dùng cú pháp CSS @import (không phải Sass @use) — package ship sẵn @source trỏ vào chính bundle của @asc/ui bên trong file này, để Tailwind tự động nhận diện các class Tailwind dùng trong component của lib. Nếu dùng @use thay vì @import, Sass sẽ inline/flatten nội dung trước khi tới Tailwind và @source sẽ không còn resolve đúng — lúc đó bạn sẽ phải tự thêm @source '../node_modules/@aseansc-admin/ui'; thủ công trong app của mình.

3. postcss.config.json (workspace root)

{
  "plugins": {
    "@tailwindcss/postcss": {}
  }
}

4. Root component template

<asc-toast />
<asc-confirm-dialog />
<router-outlet />

Components

Layout — AscLayout

import { AscLayout } from '@aseansc-admin/ui';
<asc-layout [menuItems]="menuItems" [topBarLogo]="logoTpl" [breadCrumb]="breadcrumbTpl">
  <router-outlet />
</asc-layout>

<ng-template #breadcrumbTpl>
  <asc-breadcrumb />
</ng-template>

Toggle dark mode via the sun/moon button in the topbar — adds/removes .app-dark on <html>.


Breadcrumb — AscBreadcrumb

import { AscBreadcrumb } from '@aseansc-admin/ui';

Auto-router mode — khai báo data.breadcrumb trong route config, không cần truyền gì thêm:

// app.routes.ts
{
  path: 'form',
  data: { breadcrumb: 'Form' },
  children: [
    { path: 'input', data: { breadcrumb: 'Input' }, loadComponent: ... },
  ],
}
<asc-breadcrumb />

Manual mode — truyền items thủ công:

<asc-breadcrumb
  [items]="[{ label: 'Admin', routerLink: '/admin' }, { label: 'Users' }]"
  [home]="{ icon: 'pi pi-home', routerLink: '/' }"
/>

<!-- Ẩn home icon -->
<asc-breadcrumb [items]="items" [home]="null" />

Form

Auto Complete — AscAutoCompleteComponent

<!-- Single -->
<asc-form-field label="Nhân viên">
  <asc-auto-complete
    [formControl]="ctrl"
    [suggestions]="results"
    optionLabel="fullName"
    placeholder="Tìm kiếm..."
    (completeMethod)="onSearch($event)"
  />
</asc-form-field>

<!-- Multiple (chip mode) -->
<asc-auto-complete
  [formControl]="ctrl"
  [suggestions]="results"
  optionLabel="name"
  [multiple]="true"
  (completeMethod)="onSearch($event)"
/>
results = signal<Employee[]>([]);

onSearch(query: string) {
  this.results.set(
    this.employees.filter(e => e.fullName.toLowerCase().includes(query.toLowerCase()))
  );
}

| Input | Type | Default | Mô tả | |-----------------|-----------|---------|----------------------------------------------------------| | suggestions | any[] | [] | Danh sách gợi ý — cập nhật từ (completeMethod) | | optionLabel | string | '' | Property hiển thị khi value là object | | optionValue | string | '' | Property dùng làm value (mặc định: cả object) | | minLength | number | 1 | Số ký tự tối thiểu trước khi tìm kiếm | | delay | number | 300 | Delay (ms) trước khi emit | | multiple | boolean | false | Chọn nhiều — hiển thị dạng chip | | forceSelection| boolean | false | Bắt buộc chọn từ danh sách | | dropdown | boolean | false | Hiển thị nút dropdown | | showClear | boolean | false | Hiển thị nút xoá |

| Output | Mô tả | |------------------|------------------------------------------| | completeMethod | Emit text user đang gõ (string) |

Input Mask — AscInputMaskComponent

Input với định dạng cố định. Ký tự mask: 9=chữ số · a=chữ cái · *=alphanumeric · ?=optional từ ký tự đó.

<!-- Số điện thoại -->
<asc-form-field label="Số điện thoại">
  <asc-input-mask [formControl]="ctrl" mask="0999 999 999" placeholder="0xxx xxx xxx" />
</asc-form-field>

<!-- Ngày tháng -->
<asc-input-mask [formControl]="ctrl" mask="99/99/9999" placeholder="dd/mm/yyyy" />

<!-- CCCD 12 số -->
<asc-input-mask [formControl]="ctrl" mask="999999999999" />

<!-- MST doanh nghiệp -->
<asc-input-mask [formControl]="ctrl" mask="9999999999-999" />

<!-- unmask=true → model lưu chuỗi thuần, không có ký tự phân cách -->
<asc-input-mask [formControl]="ctrl" mask="0999 999 999" [unmask]="true" />

| Input | Type | Default | Mô tả | |---------------|-----------|----------|-----------------------------------------------------------------------| | mask | string | required | Pattern định dạng. | | placeholder | string | '' | Placeholder khi chưa nhập. | | slotChar | string | '_' | Ký tự đại diện cho slot trống. | | autoClear | boolean | true | Tự xoá nếu blur mà chưa điền đủ mask. | | showClear | boolean | false | Hiển thị nút ×. | | readonly | boolean | false | Chỉ đọc. | | unmask | boolean | false | true → model value là chuỗi thuần không có ký tự phân cách. |

| Output | Mô tả | |------------|--------------------------------| | complete | Emit khi user điền đủ mask. |


Button — AscButtonComponent

<asc-button label="Lưu"   icon="pi pi-save"  (clicked)="onSave()" />
<asc-button label="Huỷ"   variant="outlined" severity="secondary" />
<asc-button label="Xoá"   severity="danger"  [loading]="loading" />

Input — AscInputComponent

<asc-form-field label="Email">
  <asc-input formControlName="email" placeholder="Nhập email..." />
</asc-form-field>

Textarea — AscTextareaComponent

<asc-textarea formControlName="note" [rows]="4" [maxLength]="500" [showCounter]="true" />

Input Number — AscInputNumberComponent

<asc-input-number formControlName="amount" mode="currency" currency="VND" />

Password — AscPasswordComponent

<asc-password formControlName="password" [feedback]="true" />

Select — AscSelectComponent

<asc-select formControlName="status" [options]="statusOptions" optionLabel="label" optionValue="value" />

Datepicker — AscDatepickerComponent

<asc-datepicker formControlName="date" dateFormat="dd/MM/yyyy" />

Checkbox — AscCheckboxComponent

<asc-checkbox formControlName="agree" label="Tôi đồng ý với điều khoản" />

Radio — AscRadioComponent

<asc-radio formControlName="gender" [options]="genderOptions" optionLabel="label" optionValue="value" />

Upload — AscUpload

<!-- Image (default) -->
<asc-upload formControlName="avatar" uploadMode="img" [maxSizePerFile]="2000000">
  <div empty>Kéo thả ảnh vào đây</div>
</asc-upload>

<!-- Excel / CSV -->
<asc-upload formControlName="sheet" uploadMode="excelFile" />

<!-- Word / Text -->
<asc-upload formControlName="doc" uploadMode="docFile" />

<!-- PDF only -->
<asc-upload formControlName="report" uploadMode="pdfFile" />

<!-- All document types (.pdf .doc .docx .xls .xlsx .csv .txt) -->
<asc-upload formControlName="attachment" uploadMode="file" />

<!-- Any file -->
<asc-upload formControlName="misc" uploadMode="all" />

| uploadMode | Accept | |---------------|-----------------------------------------| | img | image/* | | pdfFile | .pdf | | docFile | .doc, .docx, .txt | | excelFile | .xls, .xlsx, .csv | | file | .pdf, .doc, .docx, .xls, .xlsx, .csv, .txt | | all | */* |


Data

Table — AscTableComponent

<asc-table [data]="rows" [columns]="cols" [loading]="loading" (lazyLoad)="onLoad($event)">
  <!-- type='custom': dùng [ascTableCell]="key" để render tự do -->
  <ng-template [ascTableCell]="'status'" let-value let-row="row">
    <p-tag [value]="value" />
  </ng-template>
</asc-table>

Column types:

columns: AscTableColumn[] = [
  { field: 'name',      header: 'Tên',       type: 'avatar' },
  { field: 'email',     header: 'Email',     type: 'text' },
  { field: 'active',    header: 'Hoạt động', type: 'boolean' },
  { field: 'joinDate',  header: 'Ngày vào',  type: 'date' },
  { field: 'lastSeen',  header: 'Lần cuối',  type: 'datetime' },
  // Number — thousandSep + locale kiểm soát ký tự phân cách
  { field: 'score',  header: 'Điểm',  type: 'number',
    format: { thousandSep: true } },               // 8.000 (vi-VN mặc định)
  { field: 'score',  header: 'Điểm',  type: 'number',
    format: { thousandSep: true, locale: 'en-US' } }, // 8,000
  { field: 'score',  header: 'Điểm',  type: 'number',
    format: { thousandSep: false } },              // 8000
  // Currency
  { field: 'salary', header: 'Lương', type: 'currency',
    format: { locale: 'vi-VN' } },
  // Badge — map value → label + severity
  { field: 'status', header: 'Trạng thái', type: 'badge',
    badgeMap: {
      active:   { label: 'Hoạt động', severity: 'success' },
      inactive: { label: 'Tạm dừng',  severity: 'warn' },
    },
  },
  // Custom template
  { field: 'progress', header: 'Tiến độ', type: 'custom', templateKey: 'progress' },
];

Lazy load — pageNumber/pageSize: khi [lazy]="true", (lazyLoad) emit AscTableLazyLoadEvent gồm first/rows (chuẩn PrimeNG) pageNumber/pageSize (0-based, pageNumber = Math.floor(first / rows)) — dùng thẳng cho BE theo convention pageNumber/pageSize, khỏi phải tự tính lại ở service layer:

<asc-table [data]="rows" [columns]="cols" [lazy]="true" [totalRecords]="total" (lazyLoad)="onLoad($event)" />
onLoad({ pageNumber, pageSize }: AscTableLazyLoadEvent) {
  this.api.post({ authenType: 'getAllMeeting', data: { pageNumber, pageSize }, endpoint: 'feApi' })
    .subscribe(res => { this.rows = res.data; this.total = res.total; });
}

Icon — AscIconComponent

Render icon SVG inline (không phải <img>) bằng cách fetch qua HTTP từ app consume — cho phép style màu qua CSS currentColor và kích thước tuỳ ý. Khác với PrimeIcon (font class như pi pi-user), dùng khi cần icon SVG tuỳ chỉnh của riêng app.

<asc-icon src="/assets/icons/user.svg" size="24px" />
<asc-icon src="/assets/icons/star.svg" size="24px" (loadError)="onIconError($event)" />
onIconError(err: unknown) {
  console.warn('Không tải được icon', err);
}

| Input | Type | Default | Mô tả | |-------------|-----------|-----------|--------------------------------------------------------------------| | src | string | required | URL tới file .svg — fetch qua HTTP. | | size | string | '1em' | Kích thước CSS áp cho width/height (vd '24px', '1.5rem'). |

| Output | Mô tả | |-------------|-------------------------------------------------| | loadError | Emit khi fetch SVG lỗi (404, network...). |

SVG được fetch và cache theo src qua AscIconService (providedIn: 'root') — nhiều <asc-icon> cùng src chỉ gọi HTTP đúng 1 lần. File SVG nên dùng fill="currentColor"/stroke="currentColor" để kế thừa màu từ CSS color của phần tử cha.

Yêu cầu: app phải gọi provideHttpClient() (thường đã có sẵn nếu dùng @aseansc-admin/sea-http).

⚠️ Bảo mật: nội dung SVG được render qua DomSanitizer.bypassSecurityTrustHtml (bỏ qua sanitize) để giữ style/currentColor. Chỉ dùng src trỏ tới icon do chính app kiểm soát (vd thư mục assets nội bộ) — không truyền URL do user nhập, vì sẽ mở lỗ hổng XSS.

Best practice — đừng hardcode src rải rác khắp template. Gom hết đường dẫn icon vào 1 file const duy nhất, giống cách app thường tổ chức route path hay i18n key — lợi ích: đổi thư mục/CDN icon chỉ sửa 1 chỗ, gõ nhầm tên bị TypeScript bắt lỗi ngay, và IDE autocomplete được tên icon.

// src/app/shared/constants/app-icons.const.ts
const ICON_BASE = '/assets/icons';

export const AppIcons = {
  user: `${ICON_BASE}/user.svg`,
  star: `${ICON_BASE}/star.svg`,
  bell: `${ICON_BASE}/bell.svg`,
} as const;

export type AppIconName = keyof typeof AppIcons;
// some.component.ts
import { AppIcons } from '../shared/constants/app-icons.const';

@Component({ ... })
export class SomeComponent {
  protected readonly icons = AppIcons; // expose cho template dùng
}
<!-- some.component.html — không còn chuỗi path rải rác -->
<asc-icon [src]="icons.user" size="24px" />
<asc-icon [src]="icons.star" size="24px" />

Nếu app có nhiều icon và muốn tách theo nhóm chức năng (thay vì 1 file phẳng), chia theo domain — nav-icons.const.ts, action-icons.const.ts — rồi gộp lại nếu cần:

// shared/constants/icons/nav-icons.const.ts
export const NavIcons = { home: '/assets/icons/nav/home.svg', ... } as const;

// shared/constants/icons/action-icons.const.ts
export const ActionIcons = { delete: '/assets/icons/action/delete.svg', ... } as const;

Picklist — AscPicklistComponent

<asc-picklist [(source)]="available" [(target)]="selected"
              optionLabel="name" sourceHeader="Nguồn" targetHeader="Đã chọn" />

Avatar — AscAvatarComponent / AscAvatarGroupComponent

<!-- Ảnh -->
<asc-avatar image="/assets/user.jpg" size="large" />

<!-- Chữ viết tắt + màu nền -->
<asc-avatar label="NT" [style]="{ background: '#F74036', color: '#fff' }" />

<!-- Icon mặc định (pi-user) -->
<asc-avatar />

<!-- Nhóm avatar chồng nhau -->
<asc-avatar-group>
  <asc-avatar image="/u1.jpg" size="large" />
  <asc-avatar image="/u2.jpg" size="large" />
  <asc-avatar label="+3" size="large" [style]="{ background: '#dee2e6', color: '#495057' }" />
</asc-avatar-group>

| Input | Type | Default | Mô tả | |----------|-----------------------------------|-------------|----------------------------------------------------| | image | string | '' | URL ảnh — ưu tiên cao nhất | | label | string | '' | Chữ viết tắt — fallback khi không có image | | icon | string | '' | PrimeIcon — fallback, mặc định pi pi-user | | size | 'normal'\|'large'\|'xlarge' | 'normal' | 2rem / 3rem / 4rem | | shape | 'circle'\|'square' | 'circle' | Hình dạng | | style | Record<string, string> | {} | Inline style (màu nền/chữ) |

Timeline — AscTimelineComponent

<asc-timeline [items]="history" />
<asc-timeline [items]="history" align="alternate" />
<asc-timeline [items]="steps"   layout="horizontal" align="top" />
import { AscTimelineItem } from '@aseansc-admin/ui';

history: AscTimelineItem[] = [
  { title: 'Tạo cuộc họp',  date: '01/06/2026', status: 'info',    icon: 'pi pi-plus' },
  { title: 'Gửi lời mời',   date: '01/06/2026', status: 'info',    icon: 'pi pi-send',
    description: '12 thành viên được mời' },
  { title: 'Đang diễn ra',  date: '08/06/2026', status: 'warn',    icon: 'pi pi-spin pi-spinner' },
  { title: 'Hoàn thành',    date: '08/06/2026', status: 'success', icon: 'pi pi-check' },
];

| Input | Type | Default | Mô tả | |----------|---------------------------------------------|--------------|--------------------------------------| | items | AscTimelineItem[] | [] | Danh sách sự kiện | | align | 'left'\|'right'\|'alternate'\|'top'\|'bottom' | 'left' | Căn chỉnh content | | layout | 'vertical'\|'horizontal' | 'vertical' | Hướng timeline |

AscTimelineItem: title (required), description?, date?, icon?, status? (success/info/warn/danger/secondary).


Overlay

Toast & Confirm (programmatic)

import { AscToastService, AscConfirmService } from '@aseansc-admin/ui';

// Toast
this.toast.success('Lưu thành công');
this.toast.error('Có lỗi xảy ra');

// Confirm dialog — preset
this.confirm.delete('Xoá bản ghi này?', () => this.delete(id));
this.confirm.save('Lưu thay đổi?', () => this.save());
this.confirm.leave(() => this.router.navigate(['/']));

// Confirm dialog — generic
this.confirm.show({ message: 'Bạn có chắc?', accept: () => this.doIt() });

// Confirm popup (anchor to click event)
onDelete(event: MouseEvent) {
  this.confirm.popup(event, {
    message: 'Xoá bản ghi này?',
    accept: () => this.delete(),
  });
}

Đặt <asc-confirm-popup /> trong layout để dùng confirm.popup():

<asc-confirm-popup />

Dialog — AscDialogComponent

<asc-dialog [(visible)]="showDialog" header="Tiêu đề">
  <p>Nội dung dialog</p>
  <ng-template ascDialogFooter>
    <asc-button label="Đóng" severity="secondary" (clicked)="showDialog = false" />
  </ng-template>
</asc-dialog>

Sidebar — AscSidebarComponent

<asc-sidebar [(visible)]="showSidebar" position="right">
  <p>Nội dung sidebar</p>
</asc-sidebar>

Popover — AscPopoverComponent

<asc-button label="Mở popover" (clicked)="pop.toggle($event)" />
<asc-popover #pop>
  <p>Nội dung popover</p>
</asc-popover>

Tooltip — AscTooltipDirective

<asc-button label="Lưu" ascTooltip="Lưu thay đổi" tooltipPosition="top" />
<span ascTooltip="Thông tin thêm">Hover vào đây</span>

Panel

Tabs — AscTabsComponent / AscTabComponent

<asc-tabs [(value)]="activeTab">
  <asc-tab value="info"     label="Thông tin" icon="pi pi-user">
    <p>Nội dung thông tin...</p>
  </asc-tab>
  <asc-tab value="settings" label="Cài đặt"   icon="pi pi-cog">
    <p>Nội dung cài đặt...</p>
  </asc-tab>
</asc-tabs>

Stepper — AscStepperComponent / AscStepComponent

<asc-stepper [(value)]="activeStep">
  <asc-step [value]="1" label="Thông tin">
    <p>Bước 1...</p>
    <asc-button label="Tiếp theo" icon="pi pi-arrow-right" iconPos="right"
                (clicked)="activeStep.set(2)" />
  </asc-step>
  <asc-step [value]="2" label="Xác nhận">
    <p>Bước 2...</p>
    <asc-button label="Quay lại" severity="secondary" (clicked)="activeStep.set(1)" />
    <asc-button label="Hoàn tất" severity="success"   (clicked)="activeStep.set(3)" />
  </asc-step>
  <asc-step [value]="3" label="Hoàn thành">
    <p>Hoàn tất!</p>
  </asc-step>
</asc-stepper>

Feedback

Spinner — AscSpinnerComponent

Loading indicator tròn dùng để hiển thị trạng thái chờ.

<asc-spinner />
<asc-spinner size="lg" />
<asc-spinner size="xl" strokeWidth="2" />

| Input | Type | Default | Mô tả | |---------------|-----------------------------------|----------|------------------------| | size | 'xs'\|'sm'\|'md'\|'lg'\|'xl' | 'md' | Kích thước (16–80px) | | strokeWidth | string | '4' | Độ dày nét SVG (1–10) |

Badge — AscBadgeComponent

Label nhỏ dùng để đánh dấu trạng thái, danh mục.

<asc-badge label="Mới" />
<asc-badge label="Thành công" severity="success" />
<asc-badge label="Cảnh báo"  severity="warn" [rounded]="true" />
<asc-badge label="Lỗi"       severity="danger" icon="pi pi-times" />

| Input | Type | Default | Mô tả | |------------|---------------------------------------------------------------------------|-------------|----------------------| | label | string | (required)| Nội dung badge | | severity | 'primary'\|'success'\|'warn'\|'danger'\|'info'\|'secondary'\|'contrast' | 'primary' | Màu sắc | | rounded | boolean | false | Bo tròn hoàn toàn | | icon | string | — | PrimeIcon class |

Alert — AscAlertComponent

Thông báo inline dạng banner, hỗ trợ đóng.

<asc-alert severity="success" title="Lưu thành công" text="Dữ liệu đã được cập nhật." />
<asc-alert severity="warn" title="Cảnh báo" [closable]="true" (closed)="onDismiss()">
  <p>Nội dung <strong>phức tạp</strong> với HTML.</p>
</asc-alert>

| Input | Type | Default | Mô tả | |------------|----------------------------------------|----------|------------------------------------| | severity | 'success'\|'info'\|'warn'\|'danger' | 'info' | Loại alert | | title | string | — | Tiêu đề | | text | string | — | Nội dung văn bản | | closable | boolean | false | Hiển thị nút đóng |

| Output | Mô tả | |----------|-----------------------------| | closed | Emit khi user bấm đóng |

Skeleton — AscSkeletonComponent

Placeholder hiển thị trong khi data đang load.

<!-- Preset nhanh -->
<asc-skeleton preset="text" />
<asc-skeleton preset="avatar" />
<asc-skeleton preset="button" />

<!-- Tuỳ chỉnh -->
<asc-skeleton shape="rectangle" width="200px" height="120px" />
<asc-skeleton shape="circle"    width="64px"  height="64px" />

| Input | Type | Mô tả | |----------------|---------------------------------------------------|----------------------------------------| | preset | 'text'\|'avatar'\|'button'\|'image'\|'thumbnail'| Shortcut định kích thước sẵn | | shape | 'rectangle'\|'circle' | Hình dạng (default: 'rectangle') | | width | string | Ghi đè chiều rộng từ preset | | height | string | Ghi đè chiều cao từ preset | | borderRadius | string | Border radius tuỳ chỉnh |

Progress Bar — AscProgressBarComponent

Thanh tiến trình cho thấy % hoàn thành hoặc trạng thái đang xử lý.

<!-- Xác định giá trị -->
<asc-progress-bar [value]="75" />
<asc-progress-bar [value]="uploadPercent" unit="%" [showValue]="true" />

<!-- Indeterminate — xử lý không biết bao lâu -->
<asc-progress-bar mode="indeterminate" />

| Input | Type | Default | Mô tả | |-------------|-----------------------------------|------------------|-----------------------------| | value | number | — | Giá trị (0–100) | | mode | 'determinate'\|'indeterminate' | 'determinate' | Chế độ hiển thị | | showValue | boolean | false | Hiển thị % bên trong thanh | | unit | string | '%' | Đơn vị hiển thị | | color | string | — | Màu thanh (hex/CSS var) |

Empty State — AscEmptyStateComponent

Giao diện khi không có dữ liệu để hiển thị.

<asc-empty-state title="Không có dữ liệu" description="Chưa có bản ghi nào được tạo." />

<!-- Với action button -->
<asc-empty-state title="Chưa có cuộc họp nào" icon="pi pi-calendar">
  <asc-button label="Tạo cuộc họp" icon="pi pi-plus" (clicked)="openCreate()" />
</asc-empty-state>

| Input | Type | Mô tả | |---------------|----------|----------------------------------------------------| | title | string | (required) Tiêu đề | | icon | string | PrimeIcon class (default: 'pi pi-inbox') | | description | string | Mô tả bổ sung |

ng-content dùng cho action buttons hoặc nội dung custom.

Loading Bar — AscLoadingBarComponent

Thanh loading mỏng 3px cố định ở đầu trang, dùng kết hợp với AscLoadingService từ @aseansc-admin/sea-http để hiện/ẩn tự động theo trạng thái API call.

// app.component.ts
import { AscLoadingService } from '@aseansc-admin/sea-http';

loading = inject(AscLoadingService);
<!-- app.component.html — đặt trước mọi content -->
<asc-loading-bar [visible]="loading.isLoading()" />
<p-toast />
<p-confirmdialog />
<router-outlet />

| Input | Type | Default | Mô tả | |-----------|-----------|---------|----------------------------------------------| | visible | boolean | false | Hiển thị/ẩn bar. Thường bind với isLoading() |

Loading Overlay — AscLoadingOverlayComponent

Overlay toàn màn hình với spinner ở giữa, block hoàn toàn tương tác người dùng trong khi xử lý tác vụ nặng. Có backdrop-filter: blur(2px) và z-index 10000.

// app.component.ts
import { AscLoadingService } from '@aseansc-admin/sea-http';

loading = inject(AscLoadingService);
<!-- app.component.html -->
<asc-loading-overlay [visible]="loading.isLoading()" />
<!-- hoặc kèm message -->
<asc-loading-overlay [visible]="loading.isLoading()" message="Đang xử lý..." />
<p-toast />
<p-confirmdialog />
<router-outlet />

| Input | Type | Default | Mô tả | |-----------|-----------|---------|-----------------------------------------------------| | visible | boolean | false | Hiển thị/ẩn overlay. | | message | string | '' | Text tuỳ chọn hiển thị dưới spinner (vd: 'Đang xử lý...'). |


Validation Error Messages — AscMessageErrorPipe

AscMessageErrorPipe chuyển Angular validator error key thành human-readable message theo locale. Pipe này được AscFormFieldComponent dùng tự động — bạn không cần gọi trực tiếp khi dùng <asc-form-field>. Tuy nhiên bạn có thể dùng độc lập trong bất kỳ template nào.

Cú pháp

{{ errorKey | ascMessageError : label : control.errors }}

| Tham số | Type | Mô tả | |----------------|--------------------|----------------------------------------------------| | errorKey | string | Angular validator error key: 'required', 'email', ... | | label | string | Tên field hiển thị trong message: 'Email', 'Họ tên' | | control.errors | ValidationErrors \| null | AbstractControl.errors — để pipe đọc metadata (vd: minlength.requiredLength) |

Dùng trực tiếp trong template

import { AscMessageErrorPipe } from '@aseansc-admin/ui';

@Component({
  imports: [AscMessageErrorPipe, ReactiveFormsModule, KeyValuePipe],
  template: `
    <input [formControl]="emailCtrl" />
    @if (emailCtrl.errors && emailCtrl.touched) {
      @for (err of emailCtrl.errors | keyvalue; track err.key) {
        <span class="error">
          {{ err.key | ascMessageError : 'Email' : emailCtrl.errors }}
        </span>
      }
    }
  `,
})
export class MyForm {
  emailCtrl = new FormControl('', [Validators.required, Validators.email]);
}

Kết quả:

| Validator | Output | |-------------------------|-------------------------------------------| | Validators.required | Email là bắt buộc | | Validators.email | Email không đúng định dạng email | | Validators.minLength(6) | Email phải có ít nhất 6 ký tự | | Validators.maxLength(100) | Email không được vượt quá 100 ký tự | | Validators.min(0) | Email phải lớn hơn hoặc bằng 0 | | Validators.max(100) | Email phải nhỏ hơn hoặc bằng 100 | | Validators.pattern(...) | Email không đúng định dạng |

Built-in error keys

| Key | Validator tương ứng | |-----------------|----------------------------------------------| | required | Validators.required | | requiredTrue | Validators.requiredTrue | | email | Validators.email | | minlength | Validators.minLength(n) | | maxlength | Validators.maxLength(n) | | min | Validators.min(n) | | max | Validators.max(n) | | pattern | Validators.pattern(...) | | phone | custom validator | | url | custom validator | | dateRange | custom validator | | passwordMatch | custom validator | | unique | custom validator | | _fallback | mọi key không có trong map → "{label} không hợp lệ" |

Override message của built-in key

Truyền validation.messages vào provideAscUI() — chỉ cần khai báo key muốn đổi, các key còn lại giữ nguyên mặc định:

// app.config.ts
provideAscUI({
  locale: 'vi-VN',
  validation: {
    messages: {
      required:  (label) => `${label} không được bỏ trống`,
      minlength: (label, errors) => {
        const min = (errors?.['minlength'] as any)?.requiredLength;
        return `${label} tối thiểu ${min} ký tự`;
      },
    },
  },
})

Thêm error key cho custom validator

Tạo validator trả về object với key tùy chọn, sau đó đăng ký message tương ứng:

// validators/phone.validator.ts
export function phoneValidator(): ValidatorFn {
  return (control) => {
    const valid = /^(0[3|5|7|8|9])\d{8}$/.test(control.value);
    return valid ? null : { phone: true };   // key = 'phone'
  };
}
// validator với metadata (để pipe đọc thêm thông tin)
export function passwordStrengthValidator(minScore: number): ValidatorFn {
  return (control) => {
    const score = calcScore(control.value);
    return score >= minScore ? null : { passwordStrength: { minScore, actualScore: score } };
  };
}
// app.config.ts
provideAscUI({
  validation: {
    messages: {
      // Key khớp với key trả về từ validator
      phone: (label) =>
        `${label} không đúng định dạng (vd: 0912345678)`,

      passwordStrength: (label, errors) => {
        const { minScore, actualScore } = (errors?.['passwordStrength'] ?? {}) as any;
        return `${label} chưa đủ mạnh (điểm: ${actualScore}/${minScore})`;
      },
    },
  },
})
// dùng trong form
password = new FormControl('', [
  Validators.required,
  passwordStrengthValidator(3),
]);

Template hiển thị tự động qua <asc-form-field>:

<asc-form-field label="Mật khẩu">
  <asc-password formControlName="password" />
</asc-form-field>

Dark Mode

The library uses .app-dark on <html> to toggle dark mode. PrimeNG components and Tailwind utilities both respond to this class automatically — the dark variant is already registered by @aseansc-admin/ui/preset.css (see Setup), no extra config needed.


i18n

provideAscUI({ locale: 'vi-VN' })  // mặc định — Tiếng Việt
provideAscUI({ locale: 'en-US' })  // English

Đổi ngôn ngữ runtime — không cần reload

locale truyền vào provideAscUI() chỉ set giá trị khởi tạo. Để đổi ngôn ngữ khi app đang chạy (vd: dropdown chọn ngôn ngữ trên topbar), dùng AscLocaleService — mọi label, message, và cả translation nội bộ của PrimeNG (tên tháng, filter operator trong table, aria label...) sẽ tự cập nhật ngay lập tức, không cần reload trang.

import { AscLocaleService } from '@aseansc-admin/ui';

@Component({ ... })
export class LanguageSwitcherComponent {
  private locale = inject(AscLocaleService);

  switchToEnglish() {
    this.locale.setLocale('en-US');
  }

  // Đọc locale hiện tại (signal) — dùng để highlight nút đang active, v.v.
  protected readonly currentLocale = this.locale.locale;
}

License

UNLICENSED — Internal use only.