@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-groupImport
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ậnEventDOM nên không cầnstopPropagation()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.
