npm package: @libs-ui/components-dropdown
Description: > Component dropdown đa năng hỗ trợ nhiều chế độ hiển thị: text, radio, checkbox, group. Tích hợp sẵn tìm kiếm, validation, popover, tabs và điều khiển từ bên ngoài.
Latest version: 0.2.356-23

# @libs-ui/components-dropdown

> Component dropdown đa năng hỗ trợ nhiều chế độ hiển thị: text, radio, checkbox, group. Tích hợp sẵn tìm kiếm, validation, popover, tabs và điều khiển từ bên ngoài.

**Version:** `0.2.355-14`

## Giới thiệu

`LibsUiComponentsDropdownComponent` là một standalone Angular component cung cấp dropdown selection với nhiều chế độ hiển thị và tính năng nâng cao.

### Tính năng

- ✅ Nhiều chế độ hiển thị: text, radio, checkbox, group
- ✅ Tìm kiếm online/offline tích hợp sẵn
- ✅ Validation (required, max items selected)
- ✅ Điều khiển từ bên ngoài qua `IDropdownFunctionControlEvent`
- ✅ Hỗ trợ tabs để phân loại dữ liệu
- ✅ Tự động load dữ liệu từ API (httpRequestData)
- ✅ Auto-select first item / all items
- ✅ Hiển thị avatar, icon, image cho item
- ✅ Custom content qua ng-content
- ✅ OnPush Change Detection
- ✅ Angular Signals

## Khi nào sử dụng

- Chọn một hoặc nhiều giá trị từ danh sách dữ liệu
- Dropdown với tìm kiếm online/offline
- Chọn dữ liệu dạng nhóm (group), cây (tree), JSON tree
- Cần validation (required, max items)
- Cần điều khiển dropdown từ component cha (reset, refresh, check valid)
- Hiển thị dropdown với tabs để phân loại dữ liệu
- Dropdown với custom content (ng-content)

## Lưu ý quan trọng

- ⚠️ **Bắt buộc `[listConfig]`**: Phải cung cấp `[listConfig]` để dropdown hiển thị danh sách.
- ⚠️ **Chế độ chọn**: Sử dụng `[listKeySelected]` cho chọn đơn (text/radio), `[listMultiKeySelected]` cho chọn nhiều (checkbox/group).
- ⚠️ **Auto-load API**: Khi dùng `httpRequestData` trong `listConfig`, dropdown tự động gọi API để load dữ liệu.
- ⚠️ **FunctionControl**: Output `outFunctionsControl` emit `IDropdownFunctionControlEvent` để điều khiển dropdown từ bên ngoài.

## Cài đặt

```bash
# npm
npm install @libs-ui/components-dropdown

# yarn
yarn add @libs-ui/components-dropdown
```

## Import

```typescript
import {
  LibsUiComponentsDropdownComponent,
  IDropdownFunctionControlEvent,
  IEmitSelectKey,
  IEmitMultiKey,
  IPopoverCustomConfig,
  IDropdownTabsItem,
  IValidMaxItemSelected,
  IDropdown,
} from '@libs-ui/components-dropdown';

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

## Ví dụ

### Basic - Text

```html
<libs_ui-components-dropdown
  [labelConfig]="{ labelLeft: 'Chọn item', required: true }"
  [listConfig]="listConfig"
  [listSearchConfig]="{ noBorder: true }"
  [listMaxItemShow]="5"
  [convertItemSelected]="convertItemSelected"
  (outSelectKey)="onSelectKey($event)"
/>
```

```typescript
listConfig: IListConfigItem = {
  type: 'text',
  httpRequestData: signal<IHttpRequestConfig>({
    objectInstance: getConfigListDataDemo(),
    functionName: 'list',
    argumentsValue: [new UtilsHttpParamsRequest({ fromObject: { page: 1, per_page: 20 } })],
  }),
  configTemplateText: signal({
    fieldKey: 'id',
    notUseVirtualScroll: true,
    getValue: (item) => escapeHtml(item.name),
  }),
};
```

### Checkbox

```html
<libs_ui-components-dropdown
  [labelConfig]="{ labelLeft: 'Chọn nhiều (checkbox)', required: true }"
  [listConfig]="checkboxConfig"
  [listMaxItemShow]="5"
  [convertItemSelected]="convertItemSelected"
  (outSelectMultiKey)="onSelectMultiKey($event)"
/>
```

### Radio

```html
<libs_ui-components-dropdown
  [labelConfig]="{ labelLeft: 'Chọn item (radio)', required: true }"
  [listConfig]="radioConfig"
  [listMaxItemShow]="5"
  [convertItemSelected]="convertItemSelected"
  (outSelectKey)="onSelectKey($event)"
/>
```

### Interactive - Controls

```html
<libs_ui-components-dropdown
  [labelConfig]="{ labelLeft: 'Interactive', required: true }"
  [listConfig]="listConfig"
  [listMaxItemShow]="5"
  [validRequired]="{}"
  (outFunctionsControl)="onFunctionControl($event)"
/>
```

```typescript
dropdownControl: IDropdownFunctionControlEvent | undefined;

onFunctionControl(event: IDropdownFunctionControlEvent) {
  this.dropdownControl = event;
}

async resetDropdown() {
  await this.dropdownControl?.reset();
}

async checkValid() {
  const isValid = await this.dropdownControl?.checkIsValid();
  console.log('Valid:', isValid);
}

async refreshList() {
  await this.dropdownControl?.refreshList();
}
```

### States

```html
<!-- Disable -->
<libs_ui-components-dropdown
  [labelConfig]="{ labelLeft: 'Disable' }"
  [disable]="true"
  [listConfig]="listConfig"
/>

<!-- Readonly -->
<libs_ui-components-dropdown
  [labelConfig]="{ labelLeft: 'Readonly' }"
  [readonly]="true"
  [listConfig]="listConfig"
/>
```

## API

### libs_ui-components-dropdown

#### Inputs

| Property | Type | Default | Description |
|----------|------|---------|-------------|
| `[allowSelectItemMultiple]` | `boolean` | `undefined` | Cho phép chọn lại item đã chọn |
| `[changeValidUndefinedResetError]` | `boolean` | `undefined` | Reset error khi valid undefined |
| `[classAvatarInclude]` | `string` | `'mr-[8px]'` | Class CSS cho avatar |
| `[classInclude]` | `string` | `undefined` | Class CSS bổ sung cho container |
| `[classIncludeContent]` | `string` | `undefined` | Class CSS cho content wrapper |
| `[classIncludeIcon]` | `string` | `'ml-[8px]'` | Class CSS cho icon mũi tên |
| `[classIncludeTextDisplayWhenNoSelect]` | `string` | `'libs-ui-font-h5r'` | Class CSS cho text placeholder |
| `[convertItemSelected]` | `(item, translate?) => void` | `defaultConvert` | Hàm convert item sau khi chọn để hiển thị label |
| `[disable]` | `boolean` | `undefined` | Vô hiệu hóa dropdown |
| `[disableLabel]` | `boolean` | `undefined` | Vô hiệu hóa label |
| `[fieldGetColorAvatar]` | `string` | `undefined` | Field lấy color cho avatar |
| `[fieldGetIcon]` | `string` | `undefined` | Field lấy icon class từ item |
| `[fieldGetImage]` | `string` | `undefined` | Field lấy URL ảnh từ item |
| `[fieldGetLabel]` | `string` | `undefined` | Field lấy label từ item |
| `[fieldGetTextAvatar]` | `string` | `'username'` | Field lấy text cho avatar |
| `[fieldLabel]` | `string` | `'labelDisplay'` | Field lưu label hiển thị trên dropdown |
| `[flagMouse]` | `IFlagMouse (model)` | `{ isMouseEnter: false, isMouseEnterContent: false }` | Trạng thái mouse |
| `[flagMouseContent]` | `IFlagMouse (model)` | `undefined` | Trạng thái mouse content |
| `[focusInputSearch]` | `boolean` | `true` | Tự động focus input tìm kiếm khi mở |
| `[getLastTextAfterSpace]` | `boolean` | `undefined` | Lấy text cuối sau dấu cách |
| `[getPopoverItemSelected]` | `(item, translate?) => Promise<IPopover>` | `undefined` | Hàm lấy popover config cho item đã chọn |
| `[hasContentUnitRight]` | `boolean` | `undefined` | Có content bên phải unit |
| `[httpRequestDetailItemById]` | `IHttpRequestConfig` | `undefined` | Config HTTP request để lấy chi tiết item theo key |
| `[ignoreBorderBottom]` | `boolean` | `undefined` | Bỏ border bottom |
| `[ignoreStopPropagationEvent]` | `boolean` | `false` | Bỏ qua stopPropagation |
| `[imageSize]` | `TYPE_SIZE_AVATAR_CONFIG` | `16` | Kích thước avatar |
| `[isNgContent]` | `boolean` | `undefined` | Sử dụng ng-content thay vì giao diện mặc định |
| `[isSearchOnline]` | `boolean` | `false` | Tìm kiếm online (gọi API) |
| `[labelConfig]` | `ILabel` | `undefined` | Cấu hình label phía trên dropdown |
| `[labelPopoverConfig]` | `IPopoverOverlay` | `undefined` | Config popover cho label |
| `[labelPopoverFullWidth]` | `boolean` | `true` | Popover label full width |
| `[lengthKeys]` | `number (model)` | `0` | Số lượng keys đã chọn |
| `[linkImageError]` | `string` | `undefined` | Link ảnh khi lỗi |
| `[listBackgroundCustom]` | `string` | `undefined` | Background custom cho list |
| `[listButtonsOther]` | `Array<IButton>` | `undefined` | Các button khác trong list |
| `[listClickExactly]` | `boolean` | `undefined` | Click chính xác item |
| `[listConfig]` | `IListConfigItem` | `undefined` | Cấu hình danh sách (type, httpRequestData, template config) |
| `[listConfigHasDivider]` | `boolean` | `true` | Hiển thị divider trong list |
| `[listDividerClassInclude]` | `string` | `undefined` | Class CSS cho divider |
| `[listHasButtonUnSelectOption]` | `boolean` | `undefined` | Hiển thị nút bỏ chọn |
| `[listHiddenInputSearch]` | `boolean` | `undefined` | Ẩn input tìm kiếm |
| `[listIgnoreClassDisableDefaultWhenUseKeysDisableItem]` | `boolean` | `undefined` | Bỏ class disable mặc định khi dùng keysDisable |
| `[listKeysDisable]` | `Array<string>` | `undefined` | Danh sách keys bị disable |
| `[listKeysHidden]` | `Array<string>` | `undefined` | Danh sách keys bị ẩn |
| `[listKeySearch]` | `string` | `undefined` | Key tìm kiếm |
| `[listKeySelected]` | `unknown (model)` | `undefined` | Key item đang được chọn (single select) |
| `[listMaxItemShow]` | `number` | `5` | Số item tối đa hiển thị |
| `[listMultiKeySelected]` | `Array<unknown> (model)` | `undefined` | Danh sách keys đang được chọn (multi select) |
| `[listSearchConfig]` | `IInputSearchConfig` | `{ noBorder: true }` | Cấu hình input tìm kiếm |
| `[listSearchNoDataTemplateRef]` | `TemplateRef<unknown>` | `undefined` | Template khi không có dữ liệu tìm kiếm |
| `[listSearchPadding]` | `boolean` | `undefined` | Padding cho search |
| `[onlyEmitDataWhenReset]` | `boolean` | `undefined` | Chỉ emit sự kiện khi reset |
| `[popoverCustomConfig]` | `IPopoverCustomConfig` | `undefined` | Cấu hình popover overlay |
| `[popoverElementRefCustom]` | `HTMLElement` | `undefined` | Element ref custom cho popover |
| `[readonly]` | `boolean` | `undefined` | Chế độ chỉ đọc |
| `[resetKeyWhenSelectAllKey]` | `boolean` | `undefined` | Reset key khi select all |
| `[showBorderError]` | `boolean (model)` | `undefined` | Hiển thị border error |
| `[showError]` | `boolean` | `true` | Hiển thị error message |
| `[tabKeyActive]` | `string (model)` | `undefined` | Key tab đang active |
| `[tabsConfig]` | `Array<IDropdownTabsItem>` | `undefined` | Cấu hình tabs cho dropdown |
| `[textDisplayWhenMultiSelect]` | `string` | `'i18n_selecting_options'` | Text hiển thị khi chọn nhiều |
| `[textDisplayWhenNoSelect]` | `string` | `'i18n_select_information'` | Text hiển thị khi chưa chọn |
| `[typeShape]` | `TYPE_SHAPE_AVATAR` | `'circle'` | Hình dạng avatar |
| `[useXssFilter]` | `boolean` | `false` | Bật XSS filter |
| `[validMaxItemSelected]` | `IValidMaxItemSelected` | `undefined` | Validation số item chọn tối đa |
| `[validRequired]` | `IMessageTranslate` | `undefined` | Validation required |
| `[zIndex]` | `number` | `undefined` | z-index cho popover |

#### Outputs

| Property | Type | Description |
|----------|------|-------------|
| `(outChangStageFlagMouse)` | `IFlagMouse` | Emit khi trạng thái mouse thay đổi |
| `(outChangeTabKeyActive)` | `string \| undefined` | Emit khi tab active thay đổi |
| `(outClickButtonOther)` | `IButton` | Emit khi click button khác trong list |
| `(outDataChange)` | `Array<unknown>` | Emit khi dữ liệu list thay đổi |
| `(outFunctionsControl)` | `IDropdownFunctionControlEvent` | Emit functions để điều khiển dropdown |
| `(outSelectKey)` | `IEmitSelectKey \| undefined` | Emit khi chọn item (single select) |
| `(outSelectMultiKey)` | `IEmitMultiKey \| undefined` | Emit khi chọn items (multi select) |
| `(outShowList)` | `boolean` | Emit khi show/hide list |
| `(outValidEvent)` | `boolean` | Emit kết quả validation |

#### FunctionControl Methods

| Method | Description |
|--------|-------------|
| `checkIsValid()` | Kiểm tra validation và trả về boolean |
| `getDisable()` | Lấy trạng thái disable |
| `refreshList()` | Refresh lại danh sách dữ liệu |
| `removeList()` | Đóng popup danh sách |
| `reset()` | Reset dropdown về trạng thái ban đầu |
| `resetError()` | Xóa error hiện tại |
| `setError(message)` | Đặt error message |
| `setItemSelectedByKey(id)` | Chọn item theo key (gọi API nếu có httpRequestDetailItemById) |
| `updateLabelItemSelected(label)` | Cập nhật label của item đã chọn |

## Types & Interfaces

```typescript
interface IEmitSelectKey {
  key?: unknown;
  item?: any;
  isClickManual?: boolean;
  tabKeyActive?: string;
}

interface IEmitMultiKey {
  keys?: Array<unknown>;
  mapKeys?: Array<IEmitSelectKey>;
  isClickManual?: boolean;
  tabKeyActive?: string;
}

interface IPopoverCustomConfig {
  widthByParent?: boolean;
  parentBorderWidth?: number;
  maxHeight?: number;
  maxWidth?: number;
  direction?: TYPE_POPOVER_DIRECTION;
  ignoreArrow?: boolean;
  classInclude?: string;
  disable?: boolean;
  clickExactly?: boolean;
  paddingLeftItem?: boolean;
  timerDestroy?: number;
  position?: { mode: TYPE_POPOVER_POSITION_MODE; distance: number };
  animationConfig?: { time?: number; distance?: number };
  width?: number;
  classIncludeOverlayBody?: string;
}

interface IDropdownTabsItem {
  key: string;
  name: string;
  httpRequestData?: IHttpRequestConfig;
}

interface IValidMaxItemSelected extends IMessageTranslate {
  value: number;
}

interface IDropdown {
  listConfig: IListConfigItem;
  listBackgroundListCustom?: string;
  listMaxItemShow?: number;
  classIncludePopup?: string;
  paddingLeftItem?: boolean;
  clickExactly?: boolean;
  zIndex?: number;
  popoverCustomConfig?: IPopoverCustomConfig;
  disable?: boolean;
}

interface IDropdownFunctionControlEvent {
  checkIsValid: () => Promise<boolean>;
  resetError: () => Promise<void>;
  setError?: (message: string) => Promise<void>;
  removeList: () => Promise<void>;
  updateLabelItemSelected: (label: string) => Promise<void>;
  reset: () => Promise<void>;
  refreshList: () => Promise<void>;
  setItemSelectedByKey: (id: unknown) => Promise<void>;
  getDisable: () => Promise<boolean>;
}
```

| Type | Mô tả |
|------|--------|
| `IEmitSelectKey` | Dữ liệu emit khi chọn 1 item (single select) |
| `IEmitMultiKey` | Dữ liệu emit khi chọn nhiều items (multi select) |
| `IPopoverCustomConfig` | Cấu hình tùy chỉnh popover overlay |
| `IDropdownTabsItem` | Cấu hình cho từng tab trong dropdown |
| `IValidMaxItemSelected` | Cấu hình validation giới hạn số item được chọn |
| `IDropdown` | Interface cấu hình dropdown |
| `IDropdownFunctionControlEvent` | Interface chứa các methods để điều khiển dropdown từ component cha |

## Dependencies

| Package | Version | Mục đích |
|---------|---------|----------|
| `@libs-ui/components-avatar` | `0.2.355-14` | Hiển thị avatar trong dropdown |
| `@libs-ui/components-buttons-button` | `0.2.355-14` | Button interface |
| `@libs-ui/components-inputs-search` | `0.2.355-14` | Input tìm kiếm |
| `@libs-ui/components-inputs-valid` | `0.2.355-14` | Validation input |
| `@libs-ui/components-label` | `0.2.355-14` | Label component |
| `@libs-ui/components-list` | `0.2.355-14` | Danh sách hiển thị |
| `@libs-ui/components-popover` | `0.2.355-14` | Popover overlay |
| `@libs-ui/pipes-security-trust` | `0.2.355-14` | Security trust pipe |
| `@libs-ui/services-http-request` | `0.2.355-14` | HTTP request service |
| `@libs-ui/utils` | `0.2.355-14` | Utilities |
| `@ngx-translate/core` | `^15.0.0` | Đa ngôn ngữ |

## Công nghệ

| Technology | Version | Purpose |
|------------|---------|---------|
| Angular | 18+ | Framework |
| Angular Signals | - | State management |
| TailwindCSS | 3.x | Styling |
| OnPush | - | Change Detection |

## Demo

```bash
npx nx serve core-ui
```

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

## Unit Tests

```bash
# Chạy tests
npx nx test components-dropdown

# Coverage
npx nx test components-dropdown --coverage

# Watch mode
npx nx test components-dropdown --watch
```

## License

MIT