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

@libs-ui/components-skeleton

v0.2.357-0

Published

> Component hiển thị hiệu ứng loading skeleton với shimmer animation, hỗ trợ cấu hình layout linh hoạt theo dạng rows/cols.

Downloads

3,973

Readme

@libs-ui/components-skeleton

Component hiển thị hiệu ứng loading skeleton với shimmer animation, hỗ trợ cấu hình layout linh hoạt theo dạng rows/cols.

Giới thiệu

LibsUiComponentsSkeletonComponent là standalone Angular component giúp tạo khung xương (skeleton) loading với hiệu ứng shimmer mượt mà. Component tự động tính toán số lượng item cần render dựa trên chiều cao container cha, đồng thời hỗ trợ cấu hình layout phức tạp theo dạng rows và cols lồng nhau để tái hiện cấu trúc nội dung thực tế trước khi dữ liệu được tải về.

Tính năng

  • ✅ Hiệu ứng shimmer loading mượt mà (CSS animation)
  • ✅ Tự động tính toán số lượng items dựa trên chiều cao container cha
  • ✅ Hỗ trợ cấu hình layout phức tạp: rows và cols lồng nhau nhiều cấp
  • ✅ Custom class cho từng cấp: container, row, col, item
  • ✅ Hỗ trợ repeat ở mỗi cấp (container / row / col)
  • ✅ Standalone component, tối ưu hiệu năng với ChangeDetectionStrategy.OnPush

Khi nào sử dụng

  • Khi đang tải dữ liệu từ API và muốn hiển thị trước cấu trúc layout (perceived performance).
  • Thay thế loading spinner truyền thống để cải thiện trải nghiệm người dùng.
  • Trong danh sách items: comments, posts, products, notifications.
  • Trong màn hình chi tiết: user profile, order detail, article.
  • Trong bảng dữ liệu: table rows chưa load xong.

Cài đặt

npm install @libs-ui/components-skeleton

Import

import { LibsUiComponentsSkeletonComponent, ISkeletonConfig } from '@libs-ui/components-skeleton';

@Component({
  standalone: true,
  imports: [LibsUiComponentsSkeletonComponent],
  // ...
})
export class YourComponent {}

Ví dụ sử dụng

1. Basic — Tự động fill container

Component tự động tính toán số lượng dòng skeleton để lấp đầy container. Chỉ cần đặt trong một thẻ có chiều cao cụ thể.

<!-- Template -->
<div class="h-[300px] w-full">
  <libs_ui-components-skeleton />
</div>

2. Avatar & Lines (Horizontal)

Mô phỏng layout có avatar tròn và hai dòng nội dung bên cạnh — phù hợp cho danh sách người dùng, notifications.

// component.ts
import { ISkeletonConfig } from '@libs-ui/components-skeleton';

readonly avatarLinesConfig: ISkeletonConfig = {
  heightContainer: 118,
  classRows: 'bg-[#fff] p-[16px] rounded-[8px] !flex-row',
  styleMarginBottom: 16,
  rows: [
    {
      classRowLast: '!w-[40px] !h-[40px] mr-[12px] shrink-0',
      item: { classIncludeItem: '!rounded-[40px]' },
    },
    {
      classColsLast: 'h-full flex-col ',
      cols: [
        {
          classColLast: '!h-full mb-[8px]',
          item: { classIncludeItem: '!rounded-[4px]' },
        },
        {
          classColLast: '!h-auto',
          item: {
            classInclude: 'w-full !h-[20px]',
            classIncludeItem: '!rounded-[4px]',
          },
        },
      ],
    },
  ],
};
<!-- Template -->
<div class="h-[300px]">
  <libs_ui-components-skeleton [config]="avatarLinesConfig" />
</div>

3. Card với Thumbnail nhỏ

Layout card có tiêu đề lớn phía trên và thumbnail kèm nội dung bên dưới — phù hợp cho danh sách bài viết, sản phẩm.

// component.ts
import { ISkeletonConfig } from '@libs-ui/components-skeleton';

readonly cardThumbConfig: ISkeletonConfig = {
  heightContainer: 158,
  classRows: 'bg-[#fff] p-[16px] rounded-[8px]',
  styleMarginBottom: 16,
  rows: [
    {
      classRowLast: '!h-auto',
      item: {
        classInclude: 'w-full !h-[34px] mb-[8px]',
        classIncludeItem: '!rounded-[4px]',
      },
    },
    {
      classColsLast: 'h-full ',
      cols: [
        {
          classColLast: '!w-[80px] mr-[12px] shrink-0',
          item: { classIncludeItem: '!rounded-[4px]' },
        },
        {
          item: { classIncludeItem: '!rounded-[4px]' },
        },
      ],
    },
  ],
};
<!-- Template -->
<div class="h-[400px]">
  <libs_ui-components-skeleton [config]="cardThumbConfig" />
</div>

4. Header & Content (phức hợp)

Layout phức hợp: header gồm avatar tròn + tiêu đề, bên dưới là vùng nội dung nhiều dòng — phù hợp cho detail page.

// component.ts
import { ISkeletonConfig } from '@libs-ui/components-skeleton';

readonly headerContentConfig: ISkeletonConfig = {
  heightContainer: 202,
  classRows: 'bg-[#fff] p-[16px] rounded-[8px]',
  styleMarginBottom: 16,
  rows: [
    {
      classRowLast: '!h-auto ',
      cols: [
        {
          classColLast: '!w-[40px] !h-[40px] mr-[12px] shrink-0',
          item: { classIncludeItem: '!rounded-[40px]' },
        },
        {
          classColLast: '!h-auto',
          item: {
            classInclude: 'w-full !h-[40px] mb-[8px]',
            classIncludeItem: '!rounded-[4px]',
          },
        },
      ],
    },
    {
      classColsLast: 'h-full flex-col ',
      cols: [
        {
          classColLast: '!h-full mb-[8px]',
          item: { classIncludeItem: '!rounded-[4px]' },
        },
        {
          classColLast: '!h-auto',
          item: {
            classInclude: 'w-full !h-[20px]',
            classIncludeItem: '!rounded-[4px]',
          },
        },
      ],
    },
  ],
};
<!-- Template -->
<div class="h-[500px]">
  <libs_ui-components-skeleton [config]="headerContentConfig" />
</div>

5. Sectioned Content (Table header + Body)

Layout chia hai vùng: header cố định phía trên (không có shimmer) và vùng nội dung bên dưới — phù hợp cho table loading.

// component.ts
import { ISkeletonConfig } from '@libs-ui/components-skeleton';

readonly sectionedConfig: ISkeletonConfig = {
  rows: [
    {
      classRowLast: '!h-[40px] bg-[#fff]',
      item: { classIncludeItem: '!h-[40px] !rounded-none opacity-[0]' },
    },
    {
      classRowLast: 'h-full flex-col',
      item: { classIncludeItem: '!rounded-none' },
    },
  ],
};
<!-- Template -->
<div class="h-[400px]">
  <libs_ui-components-skeleton [config]="sectionedConfig" />
</div>

6. Small Icon & Line

Layout tối giản: icon nhỏ và một dòng nội dung bên cạnh — phù hợp cho menu items, breadcrumb.

// component.ts
import { ISkeletonConfig } from '@libs-ui/components-skeleton';

readonly iconLineConfig: ISkeletonConfig = {
  heightContainer: 116,
  classRows: 'bg-[#fff] rounded-[8px] !flex-row',
  styleMarginBottom: 16,
  rows: [
    {
      classRowLast: '!w-[32px] !h-[32px] mr-[12px] shrink-0',
      item: { classIncludeItem: '!rounded-[32px]' },
    },
    {
      classColsLast: 'h-full flex-col ',
      cols: [
        {
          classColLast: '!h-full',
          item: { classIncludeItem: '!rounded-[4px]' },
        },
      ],
    },
  ],
};
<!-- Template -->
<div class="h-[250px]">
  <libs_ui-components-skeleton [config]="iconLineConfig" />
</div>

@Input()

| Input | Type | Default | Mô tả | Ví dụ | |---|---|---|---|---| | [config] | ISkeletonConfig | {} | Object cấu hình toàn bộ layout skeleton: số lần lặp, chiều cao, rows/cols lồng nhau và class tuỳ chỉnh cho từng cấp | [config]="avatarLinesConfig" |

Types & Interfaces

import { ISkeletonConfig } from '@libs-ui/components-skeleton';

ISkeletonConfig

export interface ISkeletonConfig {
  /**
   * Số lần lặp lại toàn bộ layout.
   * Khi không set, component tự tính dựa trên chiều cao container / heightContainer.
   */
  repeat?: number;

  /**
   * Chiều cao cố định (px) cho mỗi block lặp lại.
   * Khi không set, component đọc offsetHeight của container thực.
   */
  heightContainer?: number;

  /** Class áp dụng lên wrapper container bao ngoài tất cả rows. */
  classRows?: string;

  /** Khoảng cách margin-bottom (px) giữa các block lặp (trừ block cuối). */
  styleMarginBottom?: number;

  rows?: Array<{
    /** Số lần lặp lại row này. */
    repeat?: number;

    /** Class cho row (áp dụng cho tất cả row trừ row cuối cùng). */
    classRow?: string;

    /** Class dành riêng cho row cuối cùng trong vòng lặp repeat. */
    classRowLast?: string;

    /**
     * Config skeleton item trực tiếp trong row (không qua cols).
     * Dùng khi row chỉ có 1 skeleton block duy nhất.
     */
    item?: ISkeletonItem;

    /** Class cho container bao ngoài tất cả cols (áp dụng cho tất cả trừ cuối). */
    classCols?: string;

    /** Class cho container cols dành riêng cho row cuối. */
    classColsLast?: string;

    cols?: Array<{
      /** Số lần lặp lại col này. */
      repeat?: number;

      /** Class cho col (áp dụng cho tất cả col trừ col cuối cùng). */
      classCol?: string;

      /** Class dành riêng cho col cuối cùng trong vòng lặp repeat. */
      classColLast?: string;

      /** Config skeleton item bên trong col. */
      item?: ISkeletonItem;
    }>;
  }>;
}

ISkeletonItem

interface ISkeletonItem {
  /**
   * Class áp dụng lên thẻ div skeleton trực tiếp (có class .skeleton và animation).
   * Dùng để override kích thước, border-radius của skeleton block.
   * Ví dụ: '!rounded-[40px]' cho avatar tròn, '!rounded-[4px]' cho text line.
   */
  classIncludeItem?: string;

  /**
   * Class áp dụng lên wrapper bên ngoài bao các skeleton divs.
   * Dùng để kiểm soát chiều cao wrapper khi skeleton item có height cố định.
   * Ví dụ: 'w-full !h-[20px]' để giới hạn chiều cao vùng chứa.
   */
  classInclude?: string;

  /** (Legacy) Class không khuyến nghị dùng trong code mới. */
  class?: string;

  /**
   * Khi true, tự động thêm margin-bottom 16px cho tất cả item trừ item cuối cùng.
   * Tiện dùng khi repeat > 1 và không muốn config styleMarginBottom ở cấp ngoài.
   */
  styleDefault?: boolean;
}

Logic ẩn quan trọng

Auto-calculation trong ngAfterViewInit

Component thực hiện tính toán tự động sau khi view khởi tạo (ngAfterViewInit + setTimeout):

  1. Khi config.rows rỗng hoặc không có: Tự tạo 1 row mặc định với item rỗng.
  2. Khi heightContainer không được set: Đọc offsetHeight thực tế của container DOM để làm chiều cao mỗi block.
  3. Khi repeat không được set: Tính tự động bằng công thức:
    repeat = Math.ceil(offsetHeight / (heightContainer + styleMarginBottom))
    Kết quả: skeleton tự lấp đầy container cha mà không cần set số dòng thủ công.
  4. Khi repeat được set: heightContainer được chia đều theo repeat để các block chia đều chiều cao container.

Cơ chế classRowLast / classColLast

Khi một row hoặc col có repeat > 1, phần tử cuối cùng trong vòng lặp sẽ áp dụng class classRowLast (hoặc classColLast) thay vì classRow (hoặc classCol). Điều này cho phép tuỳ chỉnh style riêng cho phần tử cuối, ví dụ: bỏ margin-bottom ở item cuối cùng.

Lưu ý quan trọng

⚠️ Container phải có chiều cao xác định: Component đọc offsetHeight của container cha để tính toán. Nếu container không có height hoặc min-height cụ thể, offsetHeight = 0 và skeleton sẽ không render đúng. Luôn bọc trong thẻ có chiều cao rõ ràng (h-[300px], min-h-[200px], hoặc h-full trong container đã có height).

⚠️ setTimeout trong ngAfterViewInit: Việc tính toán được bọc trong setTimeout (0ms) để đảm bảo DOM đã render xong và offsetHeight trả về giá trị chính xác. Điều này là bình thường và có chủ ý.

⚠️ classRowLast vs classRow: Khi repeat = 1 (mặc định), phần tử duy nhất được coi là "cả first lẫn last", tức là classRowLast sẽ được áp dụng. Thiết kế này có nghĩa là với row không repeat, bạn chỉ cần dùng classRowLast để set class cho row đó.

⚠️ Input config có transform: Input config được khai báo với transform (value) => value || {}. Truyền undefined hoặc null cho [config] sẽ được tự động chuyển thành {} (config mặc định), component sẽ không crash.

Demo

npx nx serve core-ui

Truy cập: http://localhost:4500/skeleton