@libs-ui/components-buttons-button
v0.2.357-9
Published
> Component Button đa năng với nhiều kiểu dáng, kích thước, icon và tích hợp popover/spinner.
Readme
@libs-ui/components-buttons-button
Component Button đa năng với nhiều kiểu dáng, kích thước, icon và tích hợp popover/spinner.
Giới thiệu
LibsUiComponentsButtonsButtonComponent là một Angular Standalone Component được thiết kế để hiển thị nút bấm với đầy đủ tính năng: hỗ trợ 20+ kiểu màu sắc (primary, secondary, outline, danger, link...), 4 kích thước, loading spinner tích hợp, icon trái/phải, chế độ icon-only, custom color qua buttonCustom, và tích hợp Popover. Component sử dụng Angular Signals + ChangeDetectionStrategy.OnPush để tối ưu hiệu năng.
Tính năng
- ✅ 20+ kiểu button: primary, secondary, third, outline, danger, green, violet, link và các biến thể
- ✅ 4 kích thước:
large,medium,small,smaller - ✅ Loading state với spinner tích hợp (
isPending) - ✅ Icon trái (
classIconLeft), icon phải (classIconRight) hoặc icon-only (iconOnlyType) - ✅ Hình ảnh bên trái (
imageLeft) - ✅ Custom color thông qua
buttonCustom(khitype = 'button-custom'hoặc'button-link-custom') - ✅ Tích hợp Popover (hover/click) với đầy đủ control API
- ✅ Trạng thái disabled với ngăn pointer events tùy chọn
- ✅ Trạng thái active (
isActive) - ✅ Hỗ trợ Enter key trên document (
isHandlerEnterDocumentClickButton) - ✅ Angular Signals +
ChangeDetectionStrategy.OnPush - ✅ Standalone Component, không cần NgModule
Khi nào sử dụng
- Khi cần nút bấm để kích hoạt hành động (submit form, mở dialog, điều hướng...)
- Khi cần hiển thị trạng thái loading trong lúc xử lý async operation
- Khi cần button với popover tooltip hoặc menu dropdown
- Khi cần button chỉ có icon, không có label
- Khi cần tùy chỉnh màu sắc button theo brand riêng của feature
Cài đặt
npm install @libs-ui/components-buttons-buttonImport
import {
LibsUiComponentsButtonsButtonComponent,
IButton,
TYPE_BUTTON,
TYPE_SIZE_BUTTON,
} from '@libs-ui/components-buttons-button';
@Component({
standalone: true,
imports: [LibsUiComponentsButtonsButtonComponent],
// ...
})
export class YourComponent {}Ví dụ sử dụng
Basic — Nút bấm đơn giản
<libs_ui-components-buttons-button
label="Lưu"
(outClick)="handlerSave($event)" />import { Component } from '@angular/core';
import { LibsUiComponentsButtonsButtonComponent } from '@libs-ui/components-buttons-button';
@Component({
selector: 'app-example',
standalone: true,
imports: [LibsUiComponentsButtonsButtonComponent],
templateUrl: './example.component.html',
})
export class ExampleComponent {
handlerSave(event: Event): void {
event.stopPropagation();
// xử lý lưu dữ liệu
}
}Button Types — Các kiểu màu sắc
<!-- Solid buttons -->
<libs_ui-components-buttons-button type="button-primary" label="Primary" (outClick)="handlerClick($event)" />
<libs_ui-components-buttons-button type="button-secondary" label="Secondary" (outClick)="handlerClick($event)" />
<libs_ui-components-buttons-button type="button-third" label="Third" (outClick)="handlerClick($event)" />
<libs_ui-components-buttons-button type="button-outline" label="Outline" (outClick)="handlerClick($event)" />
<libs_ui-components-buttons-button type="button-danger-high" label="Danger" (outClick)="handlerDelete($event)" />
<libs_ui-components-buttons-button type="button-danger-low" label="Cảnh báo" (outClick)="handlerWarning($event)" />
<libs_ui-components-buttons-button type="button-green" label="Xác nhận" (outClick)="handlerConfirm($event)" />
<libs_ui-components-buttons-button type="button-violet" label="Violet" (outClick)="handlerClick($event)" />
<!-- Outline variants -->
<libs_ui-components-buttons-button type="button-outline-secondary" label="Outline Secondary" (outClick)="handlerClick($event)" />
<libs_ui-components-buttons-button type="button-outline-green" label="Outline Green" (outClick)="handlerClick($event)" />
<libs_ui-components-buttons-button type="button-outline-hover-danger" label="Hover Danger" (outClick)="handlerClick($event)" />
<!-- Link style buttons -->
<libs_ui-components-buttons-button type="button-link-primary" label="Xem thêm" (outClick)="handlerClick($event)" />
<libs_ui-components-buttons-button type="button-link-danger-high" label="Xóa" (outClick)="handlerDelete($event)" />
<libs_ui-components-buttons-button type="button-link-green" label="Xác nhận" (outClick)="handlerClick($event)" />Button Sizes — Các kích thước
<libs_ui-components-buttons-button [sizeButton]="'large'" label="Large Button" (outClick)="handlerClick($event)" />
<libs_ui-components-buttons-button [sizeButton]="'medium'" label="Medium Button" (outClick)="handlerClick($event)" />
<libs_ui-components-buttons-button [sizeButton]="'small'" label="Small Button" (outClick)="handlerClick($event)" />
<libs_ui-components-buttons-button [sizeButton]="'smaller'" label="Smaller Button" (outClick)="handlerClick($event)" />Loading State — Trạng thái loading
<libs_ui-components-buttons-button
label="Lưu thông tin"
[isPending]="isSaving()"
(outClick)="handlerSave($event)" />import { Component, signal } from '@angular/core';
import { LibsUiComponentsButtonsButtonComponent } from '@libs-ui/components-buttons-button';
@Component({
selector: 'app-form',
standalone: true,
imports: [LibsUiComponentsButtonsButtonComponent],
templateUrl: './form.component.html',
})
export class FormComponent {
protected isSaving = signal(false);
handlerSave(event: Event): void {
event.stopPropagation();
this.isSaving.set(true);
// gọi API...
// sau khi xong: this.isSaving.set(false);
}
}With Icons — Button kèm icon
<!-- Icon bên trái -->
<libs_ui-components-buttons-button
label="Thêm mới"
[classIconLeft]="'libs-ui-icon-add'"
(outClick)="handlerAdd($event)" />
<!-- Icon bên phải -->
<libs_ui-components-buttons-button
label="Tiếp theo"
[classIconRight]="'libs-ui-icon-arrow-right'"
(outClick)="handlerNext($event)" />
<!-- Cả hai icon -->
<libs_ui-components-buttons-button
label="Tải xuống"
[classIconLeft]="'libs-ui-icon-download-outline'"
[classIconRight]="'libs-ui-icon-arrow-down'"
(outClick)="handlerDownload($event)" />
<!-- Chỉ icon, không label -->
<libs_ui-components-buttons-button
[classIconLeft]="'libs-ui-icon-arrange'"
[iconOnlyType]="true"
(outClick)="handlerOpenSettings($event)" />Disabled State — Trạng thái vô hiệu hóa
<libs_ui-components-buttons-button
label="Không thể bấm"
[disable]="true"
(outClick)="handlerClick($event)" />
<!-- Disable nhưng vẫn nhận pointer events (để show tooltip) -->
<libs_ui-components-buttons-button
label="Disabled nhưng có tooltip"
[disable]="true"
[ignorePointerEvent]="true"
(outClick)="handlerClick($event)" />Custom Color — Màu tùy chỉnh
<libs_ui-components-buttons-button
type="button-custom"
label="Custom Brand Color"
[buttonCustom]="customButtonConfig"
(outClick)="handlerClick($event)" />import { Component } from '@angular/core';
import { LibsUiComponentsButtonsButtonComponent } from '@libs-ui/components-buttons-button';
import { IColorButton } from '@libs-ui/services-config-project';
@Component({
selector: 'app-example',
standalone: true,
imports: [LibsUiComponentsButtonsButtonComponent],
templateUrl: './example.component.html',
})
export class ExampleComponent {
protected readonly customButtonConfig: IColorButton = {
rootColor: '#6366f1',
configStepColor: {
background: 500,
color: -500,
background_hover: 600,
},
};
handlerClick(event: Event): void {
event.stopPropagation();
}
}With Popover — Button kèm popover/tooltip
<libs_ui-components-buttons-button
label="Hover để xem tooltip"
[popover]="{
type: 'text',
mode: 'hover',
config: {
content: 'Đây là tooltip giải thích chức năng',
width: 220
}
}"
(outClick)="handlerClick($event)"
(outPopoverEvent)="handlerPopoverEvent($event)" />import { Component } from '@angular/core';
import { LibsUiComponentsButtonsButtonComponent } from '@libs-ui/components-buttons-button';
import { TYPE_POPOVER_EVENT } from '@libs-ui/components-popover';
@Component({
selector: 'app-example',
standalone: true,
imports: [LibsUiComponentsButtonsButtonComponent],
templateUrl: './example.component.html',
})
export class ExampleComponent {
handlerClick(event: Event): void {
event.stopPropagation();
}
handlerPopoverEvent(event: TYPE_POPOVER_EVENT): void {
event.stopPropagation?.();
// event: 'show' | 'hide' | 'click' | 'remove'
}
}Image Left — Button kèm hình ảnh
<libs_ui-components-buttons-button
label="Đăng nhập với Google"
[imageLeft]="{ src: '/assets/icons/google.svg', classInclude: 'mr-[8px] w-[16px] h-[16px]' }"
(outClick)="handlerLoginWithGoogle($event)" />Active State — Trạng thái đang được chọn
<libs_ui-components-buttons-button
type="button-outline"
label="Tab đang chọn"
[isActive]="true"
(outClick)="handlerSelect($event)" />Class Include — Thêm class tùy chỉnh
<libs_ui-components-buttons-button
label="Button full-width"
classInclude="w-full justify-center"
(outClick)="handlerClick($event)" />Enter Key Handler — Kích hoạt bằng phím Enter
<!-- Button sẽ được click khi user nhấn Enter trên document -->
<libs_ui-components-buttons-button
label="Xác nhận"
[isHandlerEnterDocumentClickButton]="true"
(outClick)="handlerConfirm($event)" />ViewChild — Truy cập Popover Control
import { Component, viewChild } from '@angular/core';
import {
LibsUiComponentsButtonsButtonComponent,
IPopoverFunctionControlEvent,
} from '@libs-ui/components-buttons-button';
@Component({
selector: 'app-example',
standalone: true,
imports: [LibsUiComponentsButtonsButtonComponent],
template: `
<libs_ui-components-buttons-button
#myBtn
label="Click"
(outFunctionsControl)="handlerFunctionsControl($event)"
(outClick)="handlerClick($event)" />
`,
})
export class ExampleComponent {
private readonly myBtn = viewChild<LibsUiComponentsButtonsButtonComponent>('myBtn');
handlerClick(event: Event): void {
event.stopPropagation();
// truy cập popover control qua FunctionsControl getter
const control = this.myBtn()?.FunctionsControl;
control?.hide?.();
}
handlerFunctionsControl(control: IPopoverFunctionControlEvent): void {
// lưu control để điều khiển popover từ bên ngoài
}
}@Input()
| Input | Type | Default | Mô tả | Ví dụ |
|---|---|---|---|---|
| [buttonCustom] | IColorButton | undefined | Cấu hình màu custom — bắt buộc khi type là 'button-custom' hoặc 'button-link-custom' | [buttonCustom]="{ rootColor: '#6366f1', configStepColor: { background: 500 } }" |
| [classIconLeft] | string | '' | Class CSS của icon hiển thị bên trái label | [classIconLeft]="'libs-ui-icon-add'" |
| [classIconRight] | string | '' | Class CSS của icon hiển thị bên phải label | [classIconRight]="'libs-ui-icon-arrow-right'" |
| [classInclude] | string | '' | Class CSS bổ sung gắn vào phần tử button | classInclude="w-full" |
| [classLabel] | string | '' | Class CSS bổ sung cho phần tử label bên trong | [classLabel]="'font-bold'" |
| [disable] | boolean | false | Vô hiệu hóa button, ngăn click và áp dụng style disabled | [disable]="isFormInvalid()" |
| [flagMouse] | IFlagMouse | { isMouseEnter: false, isMouseEnterContent: false, isContainerHasScroll: false } | Trạng thái con trỏ chuột từ container cha để popover hoạt động đúng khi scroll | [flagMouse]="flagMouse()" |
| [iconOnlyType] | boolean | false | Chỉ hiển thị icon, ẩn hoàn toàn label | [iconOnlyType]="true" |
| [ignoreFocusWhenInputTab] | boolean | undefined | Đặt tabindex="-1" để button không nhận focus khi nhấn phím Tab | [ignoreFocusWhenInputTab]="true" |
| [ignorePointerEvent] | boolean | undefined | Khi disable=true, vẫn cho phép pointer events (hữu ích khi cần show tooltip khi disabled) | [ignorePointerEvent]="true" |
| [ignoreSetClickWhenShowPopover] | boolean | undefined | Không set trạng thái isClick=true khi popover mở | [ignoreSetClickWhenShowPopover]="true" |
| [ignoreStopPropagationEvent] | boolean | true | Khi false, gọi event.stopPropagation() bên trong handler click của button | [ignoreStopPropagationEvent]="false" |
| [imageLeft] | { src: string; classInclude?: string } | undefined | Hiển thị hình ảnh bên trái label | [imageLeft]="{ src: '/assets/google.svg', classInclude: 'mr-[8px]' }" |
| [isActive] | boolean | undefined | Áp dụng trạng thái active lên button (thêm attribute active) | [isActive]="isSelected()" |
| [isHandlerEnterDocumentClickButton] | boolean | undefined | Lắng nghe sự kiện keyup Enter trên document và trigger click button | [isHandlerEnterDocumentClickButton]="true" |
| [isPending] | boolean | undefined | Hiển thị spinner loading, tạm thời chặn click khi đang pending | [isPending]="isLoading()" |
| [label] | string | ' ' | Nội dung text hiển thị trên button, hỗ trợ i18n key | label="Lưu" hoặc label="i18n_save" |
| [popover] | IPopover | {} | Cấu hình popover tích hợp (type, mode, config...) | [popover]="{ type: 'text', mode: 'hover', config: { content: 'Tooltip' } }" |
| [sizeButton] | TYPE_SIZE_BUTTON | 'medium' | Kích thước button: 'large', 'medium', 'small', 'smaller' | [sizeButton]="'small'" |
| [styleButton] | Record<string, any> | undefined | Inline styles trực tiếp cho phần tử <button> | [styleButton]="{ minWidth: '120px' }" |
| [styleIconLeft] | Record<string, any> | undefined | Inline styles cho icon bên trái | [styleIconLeft]="{ fontSize: '18px' }" |
| [type] | TYPE_BUTTON | 'button-primary' | Kiểu button xác định màu sắc và style tổng thể | type="button-secondary" |
| [widthLabelPopover] | number | undefined | Chiều rộng (px) của popover hiển thị khi label bị truncate | [widthLabelPopover]="200" |
| [zIndex] | number | 10 | Z-index của popover tích hợp | [zIndex]="100" |
@Output()
| Output | Type | Mô tả | Handler TS | Binding HTML |
|---|---|---|---|---|
| (outClick) | Event | Phát ra khi button được click và không bị disabled/pending | handlerClick(event: Event): void { event.stopPropagation(); /* xử lý */ } | (outClick)="handlerClick($event)" |
| (outFunctionsControl) | IPopoverFunctionControlEvent | Phát ra object chứa các hàm điều khiển popover (show, hide...) sau khi popover khởi tạo xong | handlerFunctionsControl(ctrl: IPopoverFunctionControlEvent): void { event.stopPropagation?.(); this.popoverControl = ctrl; } | (outFunctionsControl)="handlerFunctionsControl($event)" |
| (outPopoverEvent) | TYPE_POPOVER_EVENT | Phát ra các sự kiện lifecycle của popover: 'show', 'hide', 'click', 'remove' | handlerPopoverEvent(event: TYPE_POPOVER_EVENT): void { /* xử lý */ } | (outPopoverEvent)="handlerPopoverEvent($event)" |
Public Getter (FunctionsControl)
Truy cập qua viewChild để điều khiển popover từ bên ngoài component:
private readonly buttonRef = viewChild<LibsUiComponentsButtonsButtonComponent>('buttonRef');
// Trong template:
// <libs_ui-components-buttons-button #buttonRef ... />
// Trong TS:
const control = this.buttonRef()?.FunctionsControl;
control?.hide?.(); // ẩn popover
control?.show?.(); // hiện popoverTypes & Interfaces
import {
IButton,
TYPE_BUTTON,
TYPE_SIZE_BUTTON,
} from '@libs-ui/components-buttons-button';TYPE_BUTTON
export type TYPE_BUTTON =
// Solid buttons
| 'button-primary' // Nút chính màu xanh lam
| 'button-primary-revert' // Biến thể revert của primary
| 'button-secondary' // Nút phụ
| 'button-secondary-red' // Nút phụ màu đỏ
| 'button-secondary-green' // Nút phụ màu xanh lá
| 'button-third' // Nút cấp 3 (ít nổi bật hơn)
| 'button-outline' // Nút viền
| 'button-outline-secondary' // Nút viền secondary
| 'button-outline-green' // Nút viền xanh lá
| 'button-outline-hover-danger' // Nút viền, hover chuyển đỏ
| 'button-third-hover-danger' // Nút third, hover chuyển đỏ
| 'button-danger-high' // Nút nguy hiểm cao (đỏ đậm)
| 'button-danger-low' // Nút nguy hiểm thấp (đỏ nhạt)
| 'button-green' // Nút xanh lá (thành công)
| 'button-violet' // Nút tím
| 'button-custom' // Màu tùy chỉnh — bắt buộc truyền [buttonCustom]
// Link-style buttons (không có nền, dạng text link)
| 'button-link-primary'
| 'button-link-third'
| 'button-link-danger-high'
| 'button-link-danger-low'
| 'button-link-green'
| 'button-link-violet'
| 'button-link-custom' // Link màu tùy chỉnh — bắt buộc truyền [buttonCustom]
| string; // Hỗ trợ type tùy chỉnh mở rộngTYPE_SIZE_BUTTON
export type TYPE_SIZE_BUTTON = 'large' | 'medium' | 'small' | 'smaller';IButton
Interface dùng để cấu hình button trong danh sách (ví dụ: toolbar actions, button group):
import { IButton } from '@libs-ui/components-buttons-button';
import { IColorButton } from '@libs-ui/services-config-project';
import { IPopover } from '@libs-ui/components-popover';
export interface IButton {
key?: string;
type?: TYPE_BUTTON;
sizeButton?: TYPE_SIZE_BUTTON;
iconOnlyType?: boolean;
label?: string;
disable?: boolean;
classInclude?: string;
classIconLeft?: string;
classIconRight?: string;
classLabel?: string;
popover?: IPopover;
ignoreStopPropagationEvent?: boolean;
zIndex?: number;
isPending?: boolean;
action?: (data?: any) => Promise<void>;
styleIconLeft?: Record<string, any>;
styleButton?: Record<string, any>;
buttonCustom?: IColorButton;
}Ví dụ sử dụng IButton để cấu hình danh sách toolbar:
import { Component } from '@angular/core';
import {
LibsUiComponentsButtonsButtonComponent,
IButton,
} from '@libs-ui/components-buttons-button';
@Component({
selector: 'app-toolbar',
standalone: true,
imports: [LibsUiComponentsButtonsButtonComponent],
template: `
@for (btn of toolbarButtons; track btn.key) {
<libs_ui-components-buttons-button
[type]="btn.type || 'button-primary'"
[label]="btn.label || ''"
[classIconLeft]="btn.classIconLeft || ''"
[disable]="btn.disable || false"
(outClick)="handlerToolbarAction($event, btn)" />
}
`,
})
export class ToolbarComponent {
protected readonly toolbarButtons: IButton[] = [
{ key: 'save', type: 'button-primary', label: 'Lưu', classIconLeft: 'libs-ui-icon-download-outline' },
{ key: 'cancel', type: 'button-secondary', label: 'Hủy' },
{ key: 'delete', type: 'button-danger-high', label: 'Xóa', classIconLeft: 'libs-ui-icon-remove' },
];
handlerToolbarAction(event: Event, btn: IButton): void {
event.stopPropagation();
btn.action?.();
}
}Lưu ý quan trọng
⚠️ buttonCustom bắt buộc với custom type: Khi type="button-custom" hoặc type="button-link-custom", bắt buộc phải truyền [buttonCustom] với rootColor và configStepColor. Thiếu config này sẽ khiến button không hiển thị màu sắc.
⚠️ ignoreStopPropagationEvent mặc định là true: Mặc định button KHÔNG gọi event.stopPropagation(). Nếu muốn chặn event lan ra container cha, set [ignoreStopPropagationEvent]="false". Trong handler (outClick) phía consumer, nên tự gọi event.stopPropagation() để chắc chắn.
⚠️ isHandlerEnterDocumentClickButton listener global: Input này đăng ký keyup listener trên toàn bộ document. Chỉ dùng cho button duy nhất trong context cần xác nhận bằng Enter (ví dụ: nút OK trong dialog confirm). Không bật đồng thời nhiều button với flag này.
⚠️ isPending chặn click: Khi isPending=true, button hiển thị spinner và tự động bỏ qua mọi sự kiện click — không cần thêm [disable].
⚠️ iconOnlyType ẩn label hoàn toàn: Khi iconOnlyType=true, label sẽ không được render dù có truyền label. Cần truyền ít nhất classIconLeft hoặc classIconRight.
⚠️ Selector dùng dấu gạch dưới: Selector chính xác là libs_ui-components-buttons-button (dấu _ sau libs), không phải libs-ui-components-buttons-button.
Unit Tests
# Chạy test cho lib này
npx nx test components-buttons-button --testFile=libs-ui/components/buttons/button/src/button.component.spec.ts
# Toàn bộ test
npx nx test components-buttons-button