@libs-ui/components-card
v0.2.357-4
Published
> Component card có header collapsible, hỗ trợ custom template, two-way binding và điều khiển từ bên ngoài.
Readme
@libs-ui/components-card
Component card có header collapsible, hỗ trợ custom template, two-way binding và điều khiển từ bên ngoài.
Giới thiệu
LibsUiComponentsCardComponent là standalone Angular component hiển thị nội dung trong container gồm header và body, với khả năng collapse/expand. Header hỗ trợ label tùy chỉnh, icon collapse (trái hoặc phải), custom template, và cho phép component cha điều khiển trạng thái thông qua IFunctionControlCard.
Ngoài component chính, lib còn cung cấp LibsUiComponentsCardWrapperComponent — wrapper đơn giản hóa API dành cho các form section có shadow và label chuẩn thiết kế.
Tính năng
- Header có thể collapse/expand với animation icon
- Two-way binding trạng thái ẩn/hiện qua
model()([(isHidden)]) - Custom template cho header (override label)
- Điều khiển card từ component cha qua
IFunctionControlCard - Cấu hình styling linh hoạt qua
IConfigCard(border, background, icon position, class override) - Sub-component
CardWrappervới API đơn giản, shadow, border error state - Standalone component, ChangeDetectionStrategy.OnPush, Angular Signals
Khi nào sử dụng
- Hiển thị nội dung trong container có header và body
- Cần section có thể đóng/mở trong form hoặc dashboard
- Nhóm các trường thông tin liên quan với tiêu đề rõ ràng
- Form section cần border error khi có lỗi validation (dùng CardWrapper)
Cài đặt
npm install @libs-ui/components-cardImport
import {
LibsUiComponentsCardComponent,
LibsUiComponentsCardWrapperComponent,
IConfigCard,
IFunctionControlCard,
} from '@libs-ui/components-card';
@Component({
standalone: true,
imports: [LibsUiComponentsCardComponent, LibsUiComponentsCardWrapperComponent],
})
export class YourComponent {}Ví dụ sử dụng
1. Card cơ bản
<libs_ui-components-card [label]="{ labelLeft: 'Thông tin cơ bản' }">
<p class="text-gray-700">Nội dung card hiển thị ở đây.</p>
</libs_ui-components-card>import { Component } from '@angular/core';
import { LibsUiComponentsCardComponent } from '@libs-ui/components-card';
@Component({
selector: 'app-example',
standalone: true,
imports: [LibsUiComponentsCardComponent],
templateUrl: './example.component.html',
})
export class ExampleComponent {}2. Card có nút collapse (two-way binding)
<libs_ui-components-card
[label]="{ labelLeft: 'Thông tin hợp đồng', required: true }"
[hasCollapseBtn]="true"
[(isHidden)]="isCardHidden"
(outChangeStateShowContent)="handlerChangeState($event)">
<div class="p-4">
<p class="text-gray-700">Nội dung có thể ẩn/hiện.</p>
<p class="text-sm text-gray-500 mt-2">Trạng thái: {{ isCardHidden() ? 'Đang ẩn' : 'Đang hiện' }}</p>
</div>
</libs_ui-components-card>import { Component, signal } from '@angular/core';
import { LibsUiComponentsCardComponent } from '@libs-ui/components-card';
@Component({
selector: 'app-example',
standalone: true,
imports: [LibsUiComponentsCardComponent],
templateUrl: './example.component.html',
})
export class ExampleComponent {
protected isCardHidden = signal<boolean>(false);
protected handlerChangeState(event: Event): void {
event.stopPropagation();
// isHidden signal đã được cập nhật tự động qua two-way binding
}
}3. Card với icon ở phải và custom styling
<libs_ui-components-card
[label]="{ labelLeft: 'Cấu hình nâng cao' }"
[hasCollapseBtn]="true"
[configCard]="{
iconConRight: true,
ignoreBorderHeader: false,
ignoreBackgroundHeader: false,
classIncludeLabel: 'libs-ui-font-h3s text-blue-600'
}"
[classIncludeBody]="'bg-gray-50'">
<div class="p-4">
<p class="text-gray-600">Icon collapse nằm bên phải, label màu xanh.</p>
</div>
</libs_ui-components-card>import { Component } from '@angular/core';
import { LibsUiComponentsCardComponent } from '@libs-ui/components-card';
@Component({
selector: 'app-example',
standalone: true,
imports: [LibsUiComponentsCardComponent],
templateUrl: './example.component.html',
})
export class ExampleComponent {}4. Điều khiển card từ bên ngoài (External Control)
<button
class="px-4 py-2 bg-blue-500 text-white rounded"
(click)="handlerToggleCard($event)">
Toggle Card
</button>
<libs_ui-components-card
[label]="{ labelLeft: 'Card được điều khiển từ ngoài' }"
[hasCollapseBtn]="true"
(outFunctionsControl)="handlerReceiveFunctionsControl($event)"
(outChangeStateShowContent)="handlerChangeState($event)">
<div class="p-4">
<p class="text-gray-700">Card này có thể toggle bằng nút bên ngoài.</p>
</div>
</libs_ui-components-card>import { Component, signal } from '@angular/core';
import { LibsUiComponentsCardComponent, IFunctionControlCard } from '@libs-ui/components-card';
@Component({
selector: 'app-example',
standalone: true,
imports: [LibsUiComponentsCardComponent],
templateUrl: './example.component.html',
})
export class ExampleComponent {
private cardControls = signal<IFunctionControlCard | null>(null);
protected isCardHidden = signal<boolean>(false);
protected handlerReceiveFunctionsControl(event: Event): void {
event.stopPropagation();
this.cardControls.set(event as unknown as IFunctionControlCard);
}
protected handlerToggleCard(event: Event): void {
event.stopPropagation();
this.cardControls()?.changeHidden();
}
protected handlerChangeState(event: Event): void {
event.stopPropagation();
this.isCardHidden.set(event as unknown as boolean);
}
}Lưu ý:
outFunctionsControlemitIFunctionControlCard(không phải DOM Event). Binding đúng là(outFunctionsControl)="handlerReceiveFunctionsControl($event)"với$eventkiểuIFunctionControlCard.
5. Card với custom template header
<ng-template #customHeader>
<div class="flex items-center gap-3 w-full">
<span class="libs-ui-icon-arrange text-blue-500"></span>
<span class="libs-ui-font-h3s text-gray-800">Header tùy chỉnh</span>
<span class="ml-auto text-xs text-gray-400">v2.0</span>
</div>
</ng-template>
<libs_ui-components-card
[hasCollapseBtn]="true"
[templateHeader]="customHeader">
<div class="p-4">
<p class="text-gray-700">Nội dung với header hoàn toàn tùy chỉnh.</p>
</div>
</libs_ui-components-card>import { Component } from '@angular/core';
import { LibsUiComponentsCardComponent } from '@libs-ui/components-card';
@Component({
selector: 'app-example',
standalone: true,
imports: [LibsUiComponentsCardComponent],
templateUrl: './example.component.html',
})
export class ExampleComponent {}6. CardWrapper — API đơn giản hóa
<libs_ui-components-card-wrapper
[labelConfig]="{ labelLeft: 'THÔNG TIN ĐỐI TÁC', required: true }"
[hasCollapseBtn]="true">
<div class="p-4">
<p class="text-gray-700">Wrapper với shadow, label tự động uppercase qua i18n.</p>
</div>
</libs_ui-components-card-wrapper>import { Component } from '@angular/core';
import { LibsUiComponentsCardWrapperComponent } from '@libs-ui/components-card';
@Component({
selector: 'app-example',
standalone: true,
imports: [LibsUiComponentsCardWrapperComponent],
templateUrl: './example.component.html',
})
export class ExampleComponent {}7. CardWrapper — trạng thái lỗi
<libs_ui-components-card-wrapper
[labelConfig]="{ labelLeft: 'THÔNG TIN BẮT BUỘC', required: true }"
[borderError]="hasError()"
[hasCollapseBtn]="true">
<div class="p-4">
<p class="text-red-600">Vui lòng điền đầy đủ thông tin bắt buộc.</p>
</div>
</libs_ui-components-card-wrapper>import { Component, signal } from '@angular/core';
import { LibsUiComponentsCardWrapperComponent } from '@libs-ui/components-card';
@Component({
selector: 'app-example',
standalone: true,
imports: [LibsUiComponentsCardWrapperComponent],
templateUrl: './example.component.html',
})
export class ExampleComponent {
protected hasError = signal<boolean>(true);
}@Input() — libs_ui-components-card
| Input | Type | Default | Mô tả | Ví dụ |
|---|---|---|---|---|
| [(isHidden)] | boolean (model) | false | Trạng thái ẩn/hiện content, hỗ trợ two-way binding | [(isHidden)]="isCardHidden" |
| [classIncludeBody] | string | undefined | Class CSS bổ sung cho phần body | [classIncludeBody]="'bg-gray-50 p-6'" |
| [classIncludeHeader] | string | undefined | Class CSS bổ sung cho phần header | [classIncludeHeader]="'!p-0'" |
| [classIncludeHeaderWhenHiddenContent] | string | undefined | Class CSS bổ sung cho header khi content đang bị ẩn | [classIncludeHeaderWhenHiddenContent]="'rounded-[4px]'" |
| [clickExactly] | boolean | undefined | Khi true: chỉ click icon collapse mới toggle; khi false: click toàn bộ header sẽ toggle | [clickExactly]="true" |
| [configCard] | IConfigCard | undefined | Cấu hình styling và behavior tổng hợp của card | [configCard]="{ iconConRight: true }" |
| [hasCollapseBtn] | boolean | undefined | Hiển thị nút collapse/expand | [hasCollapseBtn]="true" |
| [ignoreTitle] | boolean | undefined | Ẩn phần header, chỉ render body | [ignoreTitle]="true" |
| [label] | ILabel | undefined | Cấu hình label hiển thị trong header | [label]="{ labelLeft: 'Tiêu đề', required: true }" |
| [templateHeader] | TemplateRef | undefined | Custom template cho header; khi truyền vào, label bị bỏ qua | [templateHeader]="myHeaderTpl" |
@Output() — libs_ui-components-card
| Output | Type | Mô tả | Handler TS | Binding HTML |
|---|---|---|---|---|
| (outChangeStateShowContent) | boolean | Emit giá trị isHidden mỗi khi trạng thái ẩn/hiện thay đổi | handlerChangeState(isHidden: boolean): void { event.stopPropagation(); this.isCardHidden.set(isHidden); } | (outChangeStateShowContent)="handlerChangeState($event)" |
| (outFunctionsControl) | IFunctionControlCard | Emit object chứa các hàm điều khiển card; được emit một lần trong ngOnInit | handlerReceiveFunctionsControl(controls: IFunctionControlCard): void { event.stopPropagation(); this.cardControls.set(controls); } | (outFunctionsControl)="handlerReceiveFunctionsControl($event)" |
@Input() — libs_ui-components-card-wrapper
| Input | Type | Default | Mô tả | Ví dụ |
|---|---|---|---|---|
| [borderError] | boolean | false | Hiển thị border đỏ trạng thái lỗi | [borderError]="hasValidationError()" |
| [hasCollapseBtn] | boolean | true | Hiển thị nút collapse/expand | [hasCollapseBtn]="false" |
| [labelConfig] | { labelLeft: string; required?: boolean } | undefined | Cấu hình label cho wrapper; labelLeft sẽ được i18n translate và uppercase tự động | [labelConfig]="{ labelLeft: 'section_title', required: true }" |
| [templateHeader] | TemplateRef | undefined | Custom template cho header | [templateHeader]="myHeaderTpl" |
IFunctionControlCard — Methods
| Method | Signature | Mô tả |
|---|---|---|
| changeHidden | () => Promise<void> | Toggle trạng thái ẩn/hiện của card content từ bên ngoài |
Types & Interfaces
import {
IConfigCard,
IFunctionControlCard,
} from '@libs-ui/components-card';
import { ILabel } from '@libs-ui/components-label';interface IConfigCard {
/** Bỏ padding-left của header */
ignorePaddingLeft?: boolean;
/** Ẩn border header (top, left, right) */
ignoreBorderHeader?: boolean;
/** Ẩn background màu xanh nhạt của header */
ignoreBackgroundHeader?: boolean;
/** Đặt icon collapse ở bên phải thay vì bên trái */
iconConRight?: boolean;
/** Chiều rộng cố định cho vùng label */
width?: string;
/** Class CSS bổ sung cho label component */
classIncludeLabel?: string;
/** Class CSS bổ sung cho icon collapse */
classIconInclude?: string;
/** Class CSS cho icon khi content đang hiện */
classIconWhenShowContent?: string;
/** Class CSS cho icon khi content đang ẩn */
classIconWhenHiddenContent?: string;
/** Ẩn border-radius của header */
ignoreBorderRadiusHeader?: boolean;
/** Ẩn border của body */
ignoreBorderBody?: boolean;
}
interface IFunctionControlCard {
/** Toggle trạng thái ẩn/hiện card từ bên ngoài */
changeHidden: () => Promise<void>;
}
// ILabel được export từ @libs-ui/components-label
interface ILabel {
labelLeft?: string;
required?: boolean;
popover?: string;
}Lưu ý quan trọng
⚠️ outFunctionsControl emit một lần duy nhất: Output này emit trong ngOnInit. Lưu reference ngay vào signal khi nhận được để sử dụng sau: (outFunctionsControl)="cardControls.set($event)".
⚠️ clickExactly ảnh hưởng hành vi click: Khi [clickExactly]="true", chỉ click trực tiếp lên icon collapse mới toggle; click lên phần còn lại của header không có tác dụng. Khi false hoặc không truyền, click bất kỳ vị trí nào trên header đều toggle (nếu hasCollapseBtn là true).
⚠️ templateHeader override label: Khi truyền [templateHeader], input [label] bị bỏ qua hoàn toàn. Hai input này loại trừ nhau.
⚠️ CardWrapper tự động uppercase + i18n: labelConfig.labelLeft được pipe qua translate rồi uppercase trong template của wrapper. Truyền i18n key nếu có, hoặc truyền chuỗi thường — kết quả sẽ luôn viết hoa.
⚠️ isHidden là model() signal: Hỗ trợ cú pháp two-way binding [(isHidden)]. Có thể dùng [isHidden] (one-way) hoặc [(isHidden)] (two-way) tùy nhu cầu.
Demo
npx nx serve core-uiTruy cập: http://localhost:4500/card
