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-group

v0.2.357-11

Published

> Component nhóm nhiều button thành một group liền kề với quản lý trạng thái active tự động.

Readme

@libs-ui/components-buttons-group

Component nhóm nhiều button thành một group liền kề với quản lý trạng thái active tự động.

Giới thiệu

LibsUiComponentsButtonsGroupComponent là standalone Angular component cho phép nhóm nhiều button thành một dải liền kề. Component tự động xử lý trạng thái active/inactive theo index, hỗ trợ two-way binding, disable theo group hoặc từng button riêng lẻ, và tích hợp popover cho từng button trong group.

Tính năng

  • ✅ Nhóm nhiều button thành một dải liền kề với border styling tự động
  • ✅ Quản lý trạng thái active/inactive theo index với visual feedback rõ ràng
  • ✅ Two-way binding [(indexActive)] cho index button đang active
  • ✅ Disable toàn bộ group hoặc từng button riêng lẻ
  • ✅ Hỗ trợ icon trái/phải và chế độ icon-only cho từng button
  • ✅ Tích hợp popover cho từng button trong group
  • ✅ Click vào button đang active không emit event (tránh re-render không cần thiết)
  • ✅ Angular Signals + OnPush Change Detection tối ưu hiệu năng

Khi nào sử dụng

  • Khi cần nhóm các button liên quan thành filter group (All / Active / Completed)
  • Khi cần view switcher (List View / Grid View / Card View)
  • Khi cần segmented control hoặc tab-style buttons
  • Khi cần toolbar formatting (Bold / Italic / Underline) dạng icon-only

Cài đặt

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

Import

import { LibsUiComponentsButtonsGroupComponent } from '@libs-ui/components-buttons-group';
import { IButton } from '@libs-ui/components-buttons-button';
import { IPopoverFunctionControlEvent } from '@libs-ui/components-popover';

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

Ví dụ sử dụng

Basic — View Switcher

<libs_ui-components-buttons-group
  [buttons]="viewButtons"
  [(indexActive)]="activeView"
  (outChange)="handlerViewChange($event)"
/>
import { Component, signal } from '@angular/core';
import { LibsUiComponentsButtonsGroupComponent } from '@libs-ui/components-buttons-group';
import { IButton } from '@libs-ui/components-buttons-button';

@Component({
  selector: 'app-view-switcher',
  standalone: true,
  imports: [LibsUiComponentsButtonsGroupComponent],
  templateUrl: './view-switcher.component.html',
})
export class ViewSwitcherComponent {
  readonly activeView = signal<number>(0);

  readonly viewButtons: IButton[] = [
    { label: 'List View', key: 'list' },
    { label: 'Grid View', key: 'grid' },
    { label: 'Card View', key: 'card' },
  ];

  protected handlerViewChange(event: Event): void {
    event.stopPropagation();
    // activeView đã được cập nhật tự động qua two-way binding
  }
}

Filter Group

<libs_ui-components-buttons-group
  [buttons]="filterButtons"
  [(indexActive)]="activeFilter"
  (outChange)="handlerFilterChange($event)"
/>
<p>Đang lọc: {{ filterButtons[activeFilter()].key }}</p>
import { Component, signal } from '@angular/core';
import { LibsUiComponentsButtonsGroupComponent } from '@libs-ui/components-buttons-group';
import { IButton } from '@libs-ui/components-buttons-button';

@Component({
  selector: 'app-filter-group',
  standalone: true,
  imports: [LibsUiComponentsButtonsGroupComponent],
  templateUrl: './filter-group.component.html',
})
export class FilterGroupComponent {
  readonly activeFilter = signal<number>(0);

  readonly filterButtons: IButton[] = [
    { label: 'All', key: 'all' },
    { label: 'Active', key: 'active' },
    { label: 'Completed', key: 'completed' },
    { label: 'Archived', key: 'archived' },
  ];

  protected handlerFilterChange(button: IButton): void {
    button.key; // 'all' | 'active' | 'completed' | 'archived'
    // Thực hiện logic lọc dữ liệu theo button.key
  }
}

With Icons

<libs_ui-components-buttons-group
  [buttons]="viewButtonsWithIcons"
  [(indexActive)]="activeView"
/>
readonly viewButtonsWithIcons: IButton[] = [
  { label: 'List', key: 'list', classIconLeft: 'libs-ui-icon-list-bulleted' },
  { label: 'Grid', key: 'grid', classIconLeft: 'libs-ui-icon-table' },
  { label: 'Card', key: 'card', classIconLeft: 'libs-ui-icon-image-solid' },
];

Icon Only (Toolbar)

<libs_ui-components-buttons-group
  [buttons]="formatButtons"
  [(indexActive)]="activeFormat"
/>
readonly formatButtons: IButton[] = [
  { key: 'bold', classIconLeft: 'libs-ui-icon-editor-bold', iconOnlyType: true },
  { key: 'italic', classIconLeft: 'libs-ui-icon-editor-italic', iconOnlyType: true },
  { key: 'underline', classIconLeft: 'libs-ui-icon-editor-underlined', iconOnlyType: true },
];

Disable — Toàn bộ group

<libs_ui-components-buttons-group
  [buttons]="viewButtons"
  [disable]="true"
  [(indexActive)]="activeView"
/>

Disable — Từng button riêng lẻ

<libs_ui-components-buttons-group
  [buttons]="buttonsWithDisabled"
  [(indexActive)]="activeIndex"
/>
readonly buttonsWithDisabled: IButton[] = [
  { label: 'Option 1', key: '1' },
  { label: 'Option 2', key: '2', disable: true },
  { label: 'Option 3', key: '3' },
];

With Popover

<libs_ui-components-buttons-group
  [buttons]="buttonsWithPopover"
  [(indexActive)]="activeIndex"
  (outFunctionsControl)="handlerPopoverControl($event)"
/>
import { IPopoverFunctionControlEvent } from '@libs-ui/components-popover';

readonly buttonsWithPopover: IButton[] = [
  {
    label: 'Info',
    key: 'info',
    popover: { content: 'Thông tin chi tiết về chức năng này', mode: 'hover' },
  },
  {
    label: 'Warning',
    key: 'warning',
    popover: { content: 'Cảnh báo: thao tác này không thể hoàn tác', mode: 'hover' },
  },
];

protected handlerPopoverControl(control: IPopoverFunctionControlEvent): void {
  // Lưu lại control để thao tác popover thủ công nếu cần
  // control.showPopover(), control.removePopoverOverlay(), ...
}

@Input()

| Input | Type | Default | Mô tả | Ví dụ | |---|---|---|---|---| | [buttons] | Array<IButton> | required | Danh sách các button trong group. Không được để rỗng. | [buttons]="viewButtons" | | [disable] | boolean | false | Disable toàn bộ button group. Từng button có thể disable riêng qua IButton.disable. | [disable]="isLoading" | | [(indexActive)] | number | 0 | Index (0-based) của button đang active. Hỗ trợ two-way binding để đồng bộ state với component cha. | [(indexActive)]="activeView" |

@Output()

| Output | Type | Mô tả | Handler TS | Binding HTML | |---|---|---|---|---| | (outChange) | IButton | Emit button vừa được chọn. Không emit khi click lại button đang active. | handlerChange(button: IButton): void { button.stopPropagation?.(); /* xử lý */ } | (outChange)="handlerChange($event)" | | (outFunctionsControl) | IPopoverFunctionControlEvent | Emit API điều khiển popover khi popover của một button trong group được khởi tạo. | handlerPopoverControl(control: IPopoverFunctionControlEvent): void { control; } | (outFunctionsControl)="handlerPopoverControl($event)" |

Lưu ý outChange: Event này không emit khi click vào button đang active (index không thay đổi). Handler không nhận Event DOM nên không cần stopPropagation() trong handler này.

Types & Interfaces

IButton

Import từ @libs-ui/components-buttons-button:

import { IButton } from '@libs-ui/components-buttons-button';

export interface IButton {
  key?: string;                                        // Định danh duy nhất cho button
  type?: TYPE_BUTTON;                                  // Bị override bởi component (xem Lưu ý quan trọng)
  sizeButton?: TYPE_SIZE_BUTTON;                       // Kích thước button
  iconOnlyType?: boolean;                              // Chỉ hiển thị icon, ẩn label
  label?: string;                                      // Nội dung text hiển thị
  disable?: boolean;                                   // Disable riêng button này
  classInclude?: string;                               // Class CSS bổ sung cho button
  classIconLeft?: string;                              // Class icon bên trái label
  classIconRight?: string;                             // Class icon bên phải label
  classLabel?: string;                                 // Class CSS bổ sung cho label
  popover?: IPopover;                                  // Cấu hình popover của button
  ignoreStopPropagationEvent?: boolean;                // Không dừng propagation event
  zIndex?: number;                                     // z-index của button
  isPending?: boolean;                                 // Trạng thái loading/pending
  action?: (data?: any) => Promise<void>;              // Callback async tuỳ chỉnh
  styleIconLeft?: Record<string, any>;                 // Inline style cho icon trái
  styleButton?: Record<string, any>;                   // Inline style cho button
  buttonCustom?: IColorButton;                         // Màu tuỳ chỉnh (khi type = 'button-custom')
}

TYPE_BUTTON

import { TYPE_BUTTON } from '@libs-ui/components-buttons-button';

export type TYPE_BUTTON =
  | 'button-primary'
  | 'button-primary-revert'
  | 'button-secondary'
  | 'button-secondary-red'
  | 'button-outline-secondary'
  | 'button-third'
  | 'button-outline'
  | 'button-danger-high'
  | 'button-outline-hover-danger'
  | 'button-third-hover-danger'
  | 'button-danger-low'
  | 'button-green'
  | 'button-violet'
  | 'button-secondary-green'
  | 'button-outline-green'
  | 'button-custom'
  | 'button-link-primary'
  | 'button-link-third'
  | 'button-link-danger-high'
  | 'button-link-danger-low'
  | 'button-link-green'
  | 'button-link-violet'
  | 'button-link-custom'
  | string;

TYPE_SIZE_BUTTON

import { TYPE_SIZE_BUTTON } from '@libs-ui/components-buttons-button';

export type TYPE_SIZE_BUTTON = 'large' | 'medium' | 'small' | 'smaller';

IPopoverFunctionControlEvent

Import từ @libs-ui/components-popover (chỉ cần khi xử lý (outFunctionsControl)):

import { IPopoverFunctionControlEvent } from '@libs-ui/components-popover';

export interface IPopoverFunctionControlEvent {
  showPopover: (elementRef?: HTMLElement) => Promise<void>;
  updatePopoverOverlayPosition: () => Promise<void>;
  removePopoverOverlay: () => Promise<void>;
  getRectContainer: () => Promise<IBoundingClientRect>;
  getRectContent: () => Promise<IBoundingClientRect>;
  updatePopoverOverlay: () => Promise<void>;
}

Lưu ý quan trọng

⚠️ Type của button bị override tự động: Component tự động đặt type = 'button-primary' cho button đang active và type = 'button-primary-revert' cho các button còn lại. Giá trị type trong IButton.type không có tác dụng khi dùng trong buttons-group.

⚠️ Index-based active state: Component quản lý trạng thái active theo index (vị trí trong mảng), không theo key. Nếu mảng buttons thay đổi thứ tự phần tử, indexActive có thể trỏ sai button. Đảm bảo thứ tự mảng ổn định khi muốn giữ nguyên trạng thái active.

⚠️ Click button đang active không emit event: Khi người dùng click vào button có index trùng indexActive(), component bỏ qua và không emit outChange. Đây là behavior có chủ đích để tránh re-render không cần thiết.

⚠️ outChange không truyền DOM Event: Handler của (outChange) nhận IButton trực tiếp, không phải Event DOM — không cần và không thể gọi event.stopPropagation() trong handler này.