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-buttons-dropdown

v0.2.357-5

Published

> Dropdown button với menu tùy chọn dạng popover, hỗ trợ hai chế độ: apply ngay khi chọn hoặc chọn trước rồi xác nhận sau.

Readme

@libs-ui/components-buttons-dropdown

Dropdown button với menu tùy chọn dạng popover, hỗ trợ hai chế độ: apply ngay khi chọn hoặc chọn trước rồi xác nhận sau.

Giới thiệu

LibsUiComponentsButtonsDropdownComponent là một standalone Angular component kết hợp button và popover menu thành một khối tương tác hoàn chỉnh. Component cung cấp hai chế độ hoạt động: applyNow = true để emit ngay khi user chọn item, hoặc applyNow = false để tách biệt nút mở menu và nút xác nhận (Apply). Nội dung item được sanitize tự động qua escapeHtml để ngăn XSS.

Tính năng

  • ✅ Hai chế độ hoạt động: apply ngay (applyNow = true) hoặc chọn rồi xác nhận (applyNow = false)
  • ✅ Two-way binding cho item đang được chọn qua [(keySelected)]
  • ✅ Tùy chỉnh tên field hiển thị (fieldDisplay) và field key (keyField) — không cần format lại data
  • ✅ Hỗ trợ icon bên trái cho từng item qua fieldClassIconLeft
  • ✅ Custom sub-template cho item (subTemplate) và button lồng trong item (buttonTemplateConfig)
  • ✅ Popover cấu hình linh hoạt: hướng mở, kích thước, z-index, animation
  • ✅ Truy cập programmatic qua getter FunctionsControl để mở/đóng popup từ bên ngoài
  • ✅ XSS-safe: giá trị fieldDisplay của mọi item tự động qua escapeHtml
  • ✅ Hỗ trợ disable toàn bộ button hoặc từng item riêng lẻ
  • ✅ Angular Signals + ChangeDetectionStrategy.OnPush — hiệu năng tối ưu

Khi nào sử dụng

  • Cần button gọi menu dropdown chọn một giá trị (filter, sort, action)
  • Cần tách luồng "chọn trước — xác nhận sau" (ví dụ: chọn kỳ báo cáo rồi bấm Apply)
  • Cần action menu với icon và label cho từng mục
  • Cần dropdown lồng popover với vị trí và kích thước tùy chỉnh theo context giao diện

Cài đặt

npm install @libs-ui/components-buttons-dropdown

Import

import {
  LibsUiComponentsButtonsDropdownComponent,
  IButtonDropdown,
  IPopupConfigButtonDropdown,
} from '@libs-ui/components-buttons-dropdown';

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

Ví dụ sử dụng

1. Apply ngay — chọn là emit

// component.ts
import { Component } from '@angular/core';
import { LibsUiComponentsButtonsDropdownComponent } from '@libs-ui/components-buttons-dropdown';

@Component({
  selector: 'app-example',
  standalone: true,
  imports: [LibsUiComponentsButtonsDropdownComponent],
  templateUrl: './example.component.html',
})
export class ExampleComponent {
  readonly statusOptions = [
    { key: 'active', label: 'Đang hoạt động' },
    { key: 'inactive', label: 'Ngừng hoạt động' },
    { key: 'pending', label: 'Chờ duyệt' },
  ];

  handlerSelectStatus(event: Event, item: { key: string; label: string }): void {
    event.stopPropagation();
    console.log('Đã chọn:', item);
  }
}
<!-- example.component.html -->
<libs_ui-components-buttons-dropdown
  label="Trạng thái"
  [items]="statusOptions"
  [applyNow]="true"
  (outSelectItem)="handlerSelectStatus($event, $event)" />

2. Chọn trước — xác nhận sau (applyNow = false)

// component.ts
import { Component, signal } from '@angular/core';
import { LibsUiComponentsButtonsDropdownComponent } from '@libs-ui/components-buttons-dropdown';

@Component({
  selector: 'app-filter',
  standalone: true,
  imports: [LibsUiComponentsButtonsDropdownComponent],
  templateUrl: './filter.component.html',
})
export class FilterComponent {
  protected selectedPeriodKey = signal<string>('q1');

  readonly periodOptions = [
    { key: 'q1', label: 'Quý 1' },
    { key: 'q2', label: 'Quý 2' },
    { key: 'q3', label: 'Quý 3' },
    { key: 'q4', label: 'Quý 4' },
  ];

  handlerApplyPeriod(event: Event, item: { key: string; label: string }): void {
    event.stopPropagation();
    console.log('Áp dụng kỳ:', item);
  }
}
<!-- filter.component.html -->
<libs_ui-components-buttons-dropdown
  label="Chọn kỳ"
  [items]="periodOptions"
  [applyNow]="false"
  [(keySelected)]="selectedPeriodKey"
  (outApply)="handlerApplyPeriod($event, $event)" />

3. Custom tên field — data không cần format lại

// component.ts
import { Component } from '@angular/core';
import { LibsUiComponentsButtonsDropdownComponent } from '@libs-ui/components-buttons-dropdown';

@Component({
  selector: 'app-user-picker',
  standalone: true,
  imports: [LibsUiComponentsButtonsDropdownComponent],
  templateUrl: './user-picker.component.html',
})
export class UserPickerComponent {
  readonly users = [
    { userId: 'u1', fullName: 'Nguyễn Văn An' },
    { userId: 'u2', fullName: 'Trần Thị Bình' },
    { userId: 'u3', fullName: 'Lê Quốc Cường' },
  ];

  handlerSelectUser(event: Event, user: { userId: string; fullName: string }): void {
    event.stopPropagation();
    console.log('Người được chọn:', user.fullName);
  }
}
<!-- user-picker.component.html -->
<libs_ui-components-buttons-dropdown
  label="Chọn người dùng"
  [items]="users"
  fieldDisplay="fullName"
  keyField="userId"
  [applyNow]="true"
  (outSelectItem)="handlerSelectUser($event, $event)" />

4. Items có icon

// component.ts
import { Component } from '@angular/core';
import { LibsUiComponentsButtonsDropdownComponent } from '@libs-ui/components-buttons-dropdown';

@Component({
  selector: 'app-action-menu',
  standalone: true,
  imports: [LibsUiComponentsButtonsDropdownComponent],
  templateUrl: './action-menu.component.html',
})
export class ActionMenuComponent {
  readonly actionItems = [
    { key: 'edit', label: 'Chỉnh sửa', classIconLeft: 'libs-ui-icon-edit-line' },
    { key: 'duplicate', label: 'Nhân bản', classIconLeft: 'libs-ui-icon-copy' },
    { key: 'delete', label: 'Xóa', classIconLeft: 'libs-ui-icon-remove' },
  ];

  handlerSelectAction(event: Event, action: { key: string; label: string }): void {
    event.stopPropagation();
    console.log('Hành động:', action.key);
  }
}
<!-- action-menu.component.html -->
<libs_ui-components-buttons-dropdown
  label="Thao tác"
  [items]="actionItems"
  fieldClassIconLeft="classIconLeft"
  [applyNow]="true"
  (outSelectItem)="handlerSelectAction($event, $event)" />

5. Tùy chỉnh cấu hình popover

// component.ts
import { Component } from '@angular/core';
import {
  LibsUiComponentsButtonsDropdownComponent,
  IPopupConfigButtonDropdown,
} from '@libs-ui/components-buttons-dropdown';

@Component({
  selector: 'app-custom-popup',
  standalone: true,
  imports: [LibsUiComponentsButtonsDropdownComponent],
  templateUrl: './custom-popup.component.html',
})
export class CustomPopupComponent {
  readonly menuItems = [
    { key: '1', label: 'Mục 1' },
    { key: '2', label: 'Mục 2' },
    { key: '3', label: 'Mục 3' },
  ];

  readonly popupConfig: IPopupConfigButtonDropdown = {
    width: 280,
    maxHeight: 200,
    direction: 'bottom',
    zIndex: 1500,
    position: { mode: 'end', distance: 0 },
  };

  handlerSelectMenu(event: Event, item: { key: string }): void {
    event.stopPropagation();
    console.log('Chọn:', item.key);
  }
}
<!-- custom-popup.component.html -->
<libs_ui-components-buttons-dropdown
  label="Tùy chọn"
  [items]="menuItems"
  [popupConfig]="popupConfig"
  [applyNow]="true"
  (outSelectItem)="handlerSelectMenu($event, $event)" />

6. Các kiểu button (typeButton)

<div class="flex gap-2">
  <libs_ui-components-buttons-dropdown
    label="Primary"
    [items]="menuItems"
    typeButton="button-primary"
    [applyNow]="true" />

  <libs_ui-components-buttons-dropdown
    label="Secondary"
    [items]="menuItems"
    typeButton="button-secondary"
    [applyNow]="true" />

  <libs_ui-components-buttons-dropdown
    label="Outline"
    [items]="menuItems"
    typeButton="button-outline"
    [applyNow]="true" />
</div>

7. Truy cập programmatic qua FunctionsControl (ViewChild)

// parent.component.ts
import { Component, viewChild } from '@angular/core';
import { LibsUiComponentsButtonsDropdownComponent } from '@libs-ui/components-buttons-dropdown';

@Component({
  selector: 'app-parent',
  standalone: true,
  imports: [LibsUiComponentsButtonsDropdownComponent],
  templateUrl: './parent.component.html',
})
export class ParentComponent {
  private readonly dropdownRef = viewChild<LibsUiComponentsButtonsDropdownComponent>('dropdownRef');

  handlerOpenDropdown(event: Event): void {
    event.stopPropagation();
    this.dropdownRef()?.FunctionsControl?.show?.();
  }

  handlerCloseDropdown(event: Event): void {
    event.stopPropagation();
    this.dropdownRef()?.FunctionsControl?.hide?.();
  }
}
<!-- parent.component.html -->
<libs_ui-components-buttons-dropdown
  #dropdownRef
  label="Menu"
  [items]="menuItems"
  [applyNow]="true"
  (outFunctionsControl)="handlerFunctionsControl($event)" />

@Input()

| Input | Type | Default | Mô tả | Ví dụ | |---|---|---|---|---| | [applyNow] | boolean | false | true: emit ngay khi chọn item; false: tách nút mở menu và nút Apply | [applyNow]="true" | | [classIconLeft] | string | '' | Class icon hiển thị bên trái label trên button chính | classIconLeft="libs-ui-icon-filter" | | [classIconRight] | string | 'libs-ui-icon-move-right rotate-[90deg]' | Class icon mũi tên bên phải trên nút toggle menu (chế độ applyNow=false) | classIconRight="libs-ui-icon-chevron-right" | | [classInclude] | string | '' | Class CSS bổ sung cho button | classInclude="w-full" | | [classIncludeContainer] | string | undefined | Class CSS bổ sung cho div container bọc ngoài | classIncludeContainer="flex-1" | | [classLabel] | string | '' | Class CSS cho label text trên button | classLabel="libs-ui-font-h5m" | | [disable] | boolean | false | Disable toàn bộ button | [disable]="isLoading()" | | [fieldClass] | string | 'class' | Tên field trong object item chứa class CSS của item đó | fieldClass="cssClass" | | [fieldClassIconLeft] | string | 'classIconLeft' | Tên field trong object item chứa class icon bên trái | fieldClassIconLeft="iconClass" | | [fieldDisplay] | string | 'label' | Tên field trong object item dùng để hiển thị text | fieldDisplay="name" | | [iconOnlyType] | boolean | false | Chỉ hiển thị icon, ẩn label trên button | [iconOnlyType]="true" | | [ignoreHiddenPopoverContentWhenMouseLeave] | boolean | true | Giữ popover mở khi chuột di ra ngoài vùng content | [ignoreHiddenPopoverContentWhenMouseLeave]="false" | | [items] | Array<any> | required | Danh sách items hiển thị trong menu | [items]="options" | | [keyField] | string | 'key' | Tên field trong object item dùng làm giá trị key định danh | keyField="id" | | [(keySelected)] | string | undefined | Key của item đang được chọn — hỗ trợ two-way binding | [(keySelected)]="selectedKey" | | [label] | string | undefined | Label mặc định của button (khi chưa chọn item nào, hoặc ở chế độ applyNow=true) | label="Chọn trạng thái" | | [modePopover] | TYPE_POPOVER_MODE | 'click-toggle' | Chế độ kích hoạt popover | modePopover="click-toggle" | | [popupConfig] | IPopupConfigButtonDropdown | { width: 205, maxWidth: 250, maxHeight: 140, zIndex: 1200, direction: 'top' } | Cấu hình chi tiết cho popover (kích thước, vị trí, z-index) | [popupConfig]="myConfig" | | [showBorderBottom] | boolean | false | Hiển thị đường kẻ phân cách dưới mỗi item trong menu | [showBorderBottom]="true" | | [sizeButton] | TYPE_SIZE_BUTTON | 'medium' | Kích thước button | sizeButton="small" | | [typeButton] | TYPE_BUTTON | 'button-primary' | Kiểu button (màu sắc, style) | typeButton="button-secondary" |

@Output()

| Output | Type | Mô tả | Handler TS | Binding HTML | |---|---|---|---|---| | (outApply) | any | Emit item được chọn khi bấm nút Apply (chỉ ở chế độ applyNow = false) | handlerApply(event: Event, item: any): void { event.stopPropagation(); console.log(item); } | (outApply)="handlerApply($event, $event)" | | (outFunctionsControl) | IPopoverFunctionControlEvent | Emit object chứa các hàm điều khiển popover (show, hide) ngay sau khi popover khởi tạo | handlerFunctionsControl(event: Event, ctrl: IPopoverFunctionControlEvent): void { event.stopPropagation(); } | (outFunctionsControl)="handlerFunctionsControl($event, $event)" | | (outHover) | boolean | Emit false mỗi khi user vừa chọn xong một item | handlerHover(event: Event, state: boolean): void { event.stopPropagation(); } | (outHover)="handlerHover($event, $event)" | | (outIconEvent) | MouseEvent | Emit MouseEvent khi click vào icon của button | handlerIconClick(event: MouseEvent): void { event.stopPropagation(); } | (outIconEvent)="handlerIconClick($event)" | | (outPopoverEvent) | TYPE_POPOVER_EVENT | Emit các sự kiện lifecycle của popover (open, close, ...) | handlerPopoverEvent(event: Event, evt: TYPE_POPOVER_EVENT): void { event.stopPropagation(); } | (outPopoverEvent)="handlerPopoverEvent($event, $event)" | | (outSelectItem) | any | Emit object item khi user click chọn một mục trong menu | handlerSelectItem(event: Event, item: any): void { event.stopPropagation(); console.log(item); } | (outSelectItem)="handlerSelectItem($event, $event)" |

Public API — FunctionsControl

Truy cập qua viewChild để điều khiển popover từ component cha:

import { Component, viewChild } from '@angular/core';
import {
  LibsUiComponentsButtonsDropdownComponent,
  IButtonDropdown,
} from '@libs-ui/components-buttons-dropdown';
import { IPopoverFunctionControlEvent } from '@libs-ui/components-popover';

@Component({
  selector: 'app-demo',
  standalone: true,
  imports: [LibsUiComponentsButtonsDropdownComponent],
  template: `
    <libs_ui-components-buttons-dropdown
      #myDropdown
      label="Menu"
      [items]="items"
      [applyNow]="true"
      (outFunctionsControl)="handlerControl($event)" />
  `,
})
export class DemoComponent {
  private readonly dropdownRef = viewChild<LibsUiComponentsButtonsDropdownComponent>('myDropdown');

  protected items = [
    { key: '1', label: 'Mục 1' },
    { key: '2', label: 'Mục 2' },
  ];

  handlerControl(ctrl: IPopoverFunctionControlEvent): void {
    // ctrl được lưu bên trong component — gọi show/hide từ bên ngoài
  }

  // Mở popup từ bên ngoài
  openMenu(): void {
    this.dropdownRef()?.FunctionsControl?.show?.();
  }

  // Đóng popup từ bên ngoài
  closeMenu(): void {
    this.dropdownRef()?.FunctionsControl?.hide?.();
  }
}

| Getter | Return Type | Mô tả | |---|---|---| | FunctionsControl | IPopoverFunctionControlEvent \| undefined | Trả về object chứa các hàm điều khiển popover. undefined nếu popover chưa khởi tạo |

Cấu trúc Item

Mỗi phần tử trong mảng items hỗ trợ các thuộc tính sau:

interface DropdownItem {
  // Bắt buộc (tên field tùy theo fieldDisplay và keyField)
  label: string;        // field hiển thị — mặc định key là 'label'
  key: string;          // field key định danh — mặc định key là 'key'

  // Tùy chọn — styling
  class?: string;           // Class CSS cho span hiển thị label của item (ghi đè 'libs-ui-font-h5r')
  classIconLeft?: string;   // Class icon hiển thị bên trái label item
  classInclude?: string;    // Class CSS bổ sung cho div wrapper của item
  classRow?: string;        // Class CSS cho div row ngoài cùng của item

  // Tùy chọn — behavior
  disable?: boolean;        // Disable item (pointer-events-none + visual disabled)
  ignoreFlex?: boolean;     // Bỏ display:flex cho wrapper item
  showPopover?: boolean;    // Hiển thị tooltip/popover riêng cho item này
  popoverContent?: string;  // Nội dung tooltip khi showPopover = true

  // Tùy chọn — advanced
  subTemplate?: TemplateRef<unknown>;  // Custom Angular template render bên trong item
  buttonTemplateConfig?: {             // Render thêm một button bên trong item
    icon?: string;           // Class icon bên phải button
    iconLeft?: string;       // Class icon bên trái button
    label?: string;          // Label của button
    type?: TYPE_BUTTON;      // Kiểu button
    action: (item: DropdownItem, allItems: DropdownItem[]) => void;  // Callback khi click
  };
}

Types & Interfaces

import {
  IButtonDropdown,
  IPopupConfigButtonDropdown,
} from '@libs-ui/components-buttons-dropdown';
// IButtonDropdown — kế thừa IButton, mô tả đầy đủ inputs của component
export interface IButtonDropdown extends IButton {
  items: any[];
  fieldDisplay?: string;
  keyField?: string;
  keySelected?: string;
  applyNow?: boolean;
  showBorderBottom?: boolean;
  popupConfig?: IPopupConfigButtonDropdown;
  ignoreHiddenPopoverContentWhenMouseLeave?: boolean;
  modePopover?: TYPE_POPOVER_MODE;
}

// IPopupConfigButtonDropdown — cấu hình popover
export interface IPopupConfigButtonDropdown {
  width?: number;          // Chiều rộng cố định (px)
  maxWidth?: number;       // Chiều rộng tối đa (px)
  maxHeight?: number;      // Chiều cao tối đa của danh sách (px)
  zIndex?: number;         // z-index của popover layer
  direction?: TYPE_POPOVER_DIRECTION;  // Hướng mở: 'top' | 'bottom' | 'left' | 'right'
  timeDestroy?: number;    // Thời gian (ms) trước khi DOM popover bị destroy sau khi đóng
  widthByParent?: boolean; // Lấy chiều rộng theo parent element
  position?: {
    mode: 'start' | 'center' | 'end';  // Căn lề ngang của popup so với trigger
    distance: number;                   // Khoảng lệch theo chiều ngang (px)
  };
  classInclude?: string;   // Class CSS bổ sung cho container popover
}

Lưu ý quan trọng

⚠️ items là required: Input [items] bắt buộc phải truyền vào. Truyền mảng rỗng [] để hiển thị trạng thái "Không có dữ liệu".

⚠️ applyNow = false yêu cầu keyField: Khi dùng chế độ apply sau, component cần keyField để theo dõi item đang được chọn. Giá trị mặc định là 'key' — đảm bảo mỗi item có field key hoặc truyền keyField phù hợp.

⚠️ Label button thay đổi khi applyNow = false: Ở chế độ applyNow = false, sau khi user chọn item, label trên button chính sẽ tự động cập nhật sang tên item được chọn (dựa theo fieldDisplay). Để label cố định, dùng applyNow = true.

⚠️ XSS tự động: Giá trị của field fieldDisplay trong từng item được tự động xử lý qua escapeHtml khi truyền vào component. Không cần escape thủ công trước khi truyền.

⚠️ FunctionsControl có thể undefined: Getter FunctionsControl trả về undefined nếu popover chưa được mount. Luôn kiểm tra ?. trước khi gọi các method bên trong.

Demo

npx nx serve core-ui

Truy cập: http://localhost:4500/buttons/dropdown