ngx-ntk-icon-picker 20.25.2 → 20.25.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +669 -33
- package/fesm2022/ngx-ntk-icon-picker.mjs +163 -156
- package/fesm2022/ngx-ntk-icon-picker.mjs.map +1 -1
- package/index.d.ts +5 -5
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -1,63 +1,699 @@
|
|
|
1
|
-
#
|
|
1
|
+
# NGX NTK Icon Picker
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
**ngx-ntk-icon-picker** - Advanced Angular icon picker component with support for multiple icon libraries including FontAwesome, Material Icons, and PrimeIcons
|
|
4
4
|
|
|
5
|
-
##
|
|
5
|
+
## 📋 Overview
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
The NGX NTK Icon Picker is a powerful Angular component for selecting icons from multiple popular icon libraries. It provides a user-friendly interface with search functionality, customizable styling, and support for FontAwesome (v4, v5, v6), Material Icons, and PrimeIcons. Perfect for applications requiring icon selection capabilities.
|
|
8
|
+
|
|
9
|
+
## 🚀 Installation
|
|
8
10
|
|
|
9
11
|
```bash
|
|
10
|
-
|
|
12
|
+
npm install ngx-ntk-icon-picker
|
|
11
13
|
```
|
|
12
14
|
|
|
13
|
-
|
|
15
|
+
### Dependencies
|
|
16
|
+
|
|
17
|
+
This library requires the following peer dependencies:
|
|
14
18
|
|
|
15
19
|
```bash
|
|
16
|
-
|
|
20
|
+
npm install @fortawesome/angular-fontawesome @fortawesome/fontawesome-svg-core @fortawesome/free-solid-svg-icons primeicons
|
|
17
21
|
```
|
|
18
22
|
|
|
19
|
-
##
|
|
23
|
+
## 📦 Features
|
|
20
24
|
|
|
21
|
-
|
|
25
|
+
### Core Features
|
|
22
26
|
|
|
23
|
-
|
|
24
|
-
|
|
27
|
+
- **Multi-Icon Library Support** - FontAwesome v4, v5, v6, Material Icons, PrimeIcons
|
|
28
|
+
- **Search Functionality** - Real-time icon search and filtering
|
|
29
|
+
- **Customizable Interface** - Flexible styling and positioning options
|
|
30
|
+
- **Icon Preview** - Visual preview of selected icons
|
|
31
|
+
- **Responsive Design** - Mobile-friendly interface
|
|
32
|
+
- **Accessibility** - ARIA support and keyboard navigation
|
|
33
|
+
|
|
34
|
+
### Advanced Features
|
|
35
|
+
|
|
36
|
+
- **Custom Icon Packs** - Extensible architecture for custom icon libraries
|
|
37
|
+
- **Positioning Options** - Configurable popup positioning
|
|
38
|
+
- **Size Customization** - Adjustable icon and container sizes
|
|
39
|
+
- **Fallback Icons** - Default icon when none is selected
|
|
40
|
+
- **Search Filter Persistence** - Option to maintain search state
|
|
41
|
+
- **Template Customization** - Custom templates for icon display
|
|
42
|
+
|
|
43
|
+
## 🔧 Usage
|
|
44
|
+
|
|
45
|
+
### Basic Setup
|
|
46
|
+
|
|
47
|
+
```typescript
|
|
48
|
+
import { NgModule } from "@angular/core";
|
|
49
|
+
import { IconPickerModule } from "ngx-ntk-icon-picker";
|
|
50
|
+
|
|
51
|
+
@NgModule({
|
|
52
|
+
imports: [IconPickerModule],
|
|
53
|
+
})
|
|
54
|
+
export class AppModule {}
|
|
25
55
|
```
|
|
26
56
|
|
|
27
|
-
|
|
57
|
+
### Basic Implementation
|
|
28
58
|
|
|
29
|
-
|
|
59
|
+
```html
|
|
60
|
+
<icon-picker [(icon)]="selectedIcon" [ipIconPack]="['fa', 'fa5', 'fa6']" [ipPlaceHolder]="'Select an icon'" (iconSelected)="onIconSelected($event)"> </icon-picker>
|
|
61
|
+
```
|
|
30
62
|
|
|
31
|
-
|
|
63
|
+
### Advanced Configuration
|
|
32
64
|
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
```
|
|
65
|
+
```typescript
|
|
66
|
+
import { Component } from "@angular/core";
|
|
67
|
+
import { Icon, IconType } from "ngx-ntk-icon-picker";
|
|
37
68
|
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
69
|
+
@Component({
|
|
70
|
+
selector: "app-icon-selector",
|
|
71
|
+
template: ` <icon-picker [(icon)]="selectedIcon" [ipIconPack]="['fa', 'fa5', 'fa6', 'mat', 'pi']" [ipPosition]="'bottom'" [ipHeight]="400" [ipMaxHeight]="500" [ipWidth]="350" [ipIconSize]="20" [ipIconVerticalPadding]="8" [ipIconHorizontalPadding]="8" [ipPlaceHolder]="'Choose an icon'" [ipFallbackIcon]="'fa fa-star'" [ipKeepSearchFilter]="true" [ipButtonStyleClass]="'btn btn-primary'" [ipInputSearchStyleClass]="'form-control'" [ipDivSearchStyleClass]="'search-container'" (iconSelected)="onIconSelected($event)" (iconPickerOpen)="onIconPickerOpen()" (iconPickerClose)="onIconPickerClose()"> </icon-picker> `,
|
|
72
|
+
})
|
|
73
|
+
export class IconSelectorComponent {
|
|
74
|
+
selectedIcon: string = "fa fa-home";
|
|
42
75
|
|
|
43
|
-
|
|
76
|
+
onIconSelected(icon: Icon): void {
|
|
77
|
+
console.log("Icon selected:", icon);
|
|
78
|
+
// Handle icon selection
|
|
79
|
+
}
|
|
44
80
|
|
|
45
|
-
|
|
81
|
+
onIconPickerOpen(): void {
|
|
82
|
+
console.log("Icon picker opened");
|
|
83
|
+
}
|
|
46
84
|
|
|
47
|
-
|
|
48
|
-
|
|
85
|
+
onIconPickerClose(): void {
|
|
86
|
+
console.log("Icon picker closed");
|
|
87
|
+
}
|
|
88
|
+
}
|
|
49
89
|
```
|
|
50
90
|
|
|
51
|
-
##
|
|
91
|
+
## 📚 API Reference
|
|
52
92
|
|
|
53
|
-
|
|
93
|
+
### Input Properties
|
|
54
94
|
|
|
55
|
-
|
|
56
|
-
|
|
95
|
+
| Property | Type | Default | Description |
|
|
96
|
+
| ------------------------- | -------- | ---------------- | ------------------------------------------------- |
|
|
97
|
+
| `icon` | string | - | Selected icon value (two-way binding) |
|
|
98
|
+
| `ipIconPack` | string[] | ['fa'] | Icon packs to include |
|
|
99
|
+
| `ipPosition` | string | 'bottom' | Popup position ('top', 'bottom', 'left', 'right') |
|
|
100
|
+
| `ipHeight` | number | 300 | Popup height in pixels |
|
|
101
|
+
| `ipMaxHeight` | number | 400 | Maximum popup height in pixels |
|
|
102
|
+
| `ipWidth` | number | 250 | Popup width in pixels |
|
|
103
|
+
| `ipIconSize` | number | 16 | Icon size in pixels |
|
|
104
|
+
| `ipIconVerticalPadding` | number | 4 | Vertical padding around icons |
|
|
105
|
+
| `ipIconHorizontalPadding` | number | 4 | Horizontal padding around icons |
|
|
106
|
+
| `ipPlaceHolder` | string | 'Select an icon' | Placeholder text |
|
|
107
|
+
| `ipFallbackIcon` | string | - | Fallback icon when none selected |
|
|
108
|
+
| `ipKeepSearchFilter` | boolean | false | Keep search filter when reopening |
|
|
109
|
+
| `ipButtonStyleClass` | string | - | CSS class for the button |
|
|
110
|
+
| `ipInputSearchStyleClass` | string | - | CSS class for search input |
|
|
111
|
+
| `ipDivSearchStyleClass` | string | - | CSS class for search container |
|
|
112
|
+
| `ipUseRootViewContainer` | boolean | false | Use root view container for popup |
|
|
113
|
+
|
|
114
|
+
### Output Events
|
|
115
|
+
|
|
116
|
+
| Event | Type | Description |
|
|
117
|
+
| ----------------- | ------------ | ------------------------ |
|
|
118
|
+
| `iconSelected` | EventEmitter | Icon selection event |
|
|
119
|
+
| `iconPickerOpen` | EventEmitter | Icon picker opened event |
|
|
120
|
+
| `iconPickerClose` | EventEmitter | Icon picker closed event |
|
|
121
|
+
|
|
122
|
+
### Icon Interface
|
|
123
|
+
|
|
124
|
+
```typescript
|
|
125
|
+
export class Icon {
|
|
126
|
+
name: string; // Icon name
|
|
127
|
+
id: string; // Unique identifier
|
|
128
|
+
filter?: string[]; // Search filters
|
|
129
|
+
aliases?: string[]; // Alternative names
|
|
130
|
+
type?: IconType; // Icon library type
|
|
131
|
+
iconName?: IconName; // FontAwesome icon name
|
|
132
|
+
prefix?: string; // Icon prefix
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
export enum IconType {
|
|
136
|
+
FontAwesome, // FontAwesome v4
|
|
137
|
+
FontAwesome5 = 2, // FontAwesome v5
|
|
138
|
+
Material, // Material Icons
|
|
139
|
+
FontAwesome6, // FontAwesome v6
|
|
140
|
+
PrimeIcons, // PrimeIcons
|
|
141
|
+
}
|
|
57
142
|
```
|
|
58
143
|
|
|
59
|
-
|
|
144
|
+
## 🎨 Customization
|
|
145
|
+
|
|
146
|
+
### Custom Styling
|
|
147
|
+
|
|
148
|
+
```scss
|
|
149
|
+
// Custom icon picker styles
|
|
150
|
+
.icon-picker {
|
|
151
|
+
.icon-picker-button {
|
|
152
|
+
display: flex;
|
|
153
|
+
align-items: center;
|
|
154
|
+
gap: 8px;
|
|
155
|
+
padding: 8px 12px;
|
|
156
|
+
border: 1px solid #ddd;
|
|
157
|
+
border-radius: 4px;
|
|
158
|
+
background-color: #fff;
|
|
159
|
+
cursor: pointer;
|
|
160
|
+
transition: all 0.3s ease;
|
|
161
|
+
|
|
162
|
+
&:hover {
|
|
163
|
+
border-color: #2196f3;
|
|
164
|
+
background-color: #f5f5f5;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
.selected-icon {
|
|
168
|
+
font-size: 16px;
|
|
169
|
+
color: #333;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
.placeholder {
|
|
173
|
+
color: #999;
|
|
174
|
+
font-style: italic;
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
.icon-picker-popup {
|
|
179
|
+
position: absolute;
|
|
180
|
+
background-color: #fff;
|
|
181
|
+
border: 1px solid #ddd;
|
|
182
|
+
border-radius: 8px;
|
|
183
|
+
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
|
|
184
|
+
z-index: 1000;
|
|
185
|
+
overflow: hidden;
|
|
186
|
+
|
|
187
|
+
.search-container {
|
|
188
|
+
padding: 12px;
|
|
189
|
+
border-bottom: 1px solid #eee;
|
|
190
|
+
|
|
191
|
+
.search-input {
|
|
192
|
+
width: 100%;
|
|
193
|
+
padding: 8px 12px;
|
|
194
|
+
border: 1px solid #ddd;
|
|
195
|
+
border-radius: 4px;
|
|
196
|
+
font-size: 14px;
|
|
197
|
+
|
|
198
|
+
&:focus {
|
|
199
|
+
outline: none;
|
|
200
|
+
border-color: #2196f3;
|
|
201
|
+
box-shadow: 0 0 0 2px rgba(33, 150, 243, 0.2);
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
.icons-container {
|
|
207
|
+
max-height: 300px;
|
|
208
|
+
overflow-y: auto;
|
|
209
|
+
padding: 8px;
|
|
210
|
+
|
|
211
|
+
.icon-item {
|
|
212
|
+
display: inline-flex;
|
|
213
|
+
align-items: center;
|
|
214
|
+
justify-content: center;
|
|
215
|
+
width: 32px;
|
|
216
|
+
height: 32px;
|
|
217
|
+
margin: 2px;
|
|
218
|
+
border: 1px solid transparent;
|
|
219
|
+
border-radius: 4px;
|
|
220
|
+
cursor: pointer;
|
|
221
|
+
transition: all 0.2s ease;
|
|
222
|
+
|
|
223
|
+
&:hover {
|
|
224
|
+
border-color: #2196f3;
|
|
225
|
+
background-color: #f0f8ff;
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
&.selected {
|
|
229
|
+
border-color: #2196f3;
|
|
230
|
+
background-color: #e3f2fd;
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
i {
|
|
234
|
+
font-size: 16px;
|
|
235
|
+
color: #333;
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
```
|
|
242
|
+
|
|
243
|
+
### Custom Icon Pack
|
|
244
|
+
|
|
245
|
+
```typescript
|
|
246
|
+
import { Injectable } from "@angular/core";
|
|
247
|
+
import { Icon, IconType } from "ngx-ntk-icon-picker";
|
|
248
|
+
import { IconsPack } from "ngx-ntk-icon-picker";
|
|
249
|
+
|
|
250
|
+
@Injectable()
|
|
251
|
+
export class CustomIconsPack implements IconsPack {
|
|
252
|
+
getIcons(): Icon[] {
|
|
253
|
+
return [
|
|
254
|
+
{
|
|
255
|
+
name: "custom-icon-1",
|
|
256
|
+
id: "custom-1",
|
|
257
|
+
filter: ["custom", "icon", "one"],
|
|
258
|
+
type: IconType.FontAwesome,
|
|
259
|
+
prefix: "custom",
|
|
260
|
+
},
|
|
261
|
+
{
|
|
262
|
+
name: "custom-icon-2",
|
|
263
|
+
id: "custom-2",
|
|
264
|
+
filter: ["custom", "icon", "two"],
|
|
265
|
+
type: IconType.FontAwesome,
|
|
266
|
+
prefix: "custom",
|
|
267
|
+
},
|
|
268
|
+
];
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
```
|
|
272
|
+
|
|
273
|
+
## 🌐 Icon Libraries Support
|
|
274
|
+
|
|
275
|
+
### FontAwesome v4 (fa)
|
|
276
|
+
|
|
277
|
+
```typescript
|
|
278
|
+
// Include FontAwesome v4 icons
|
|
279
|
+
[ipIconPack] = "['fa']";
|
|
280
|
+
|
|
281
|
+
// Example icons
|
|
282
|
+
("fa fa-home");
|
|
283
|
+
("fa fa-user");
|
|
284
|
+
("fa fa-cog");
|
|
285
|
+
("fa fa-heart");
|
|
286
|
+
("fa fa-star");
|
|
287
|
+
```
|
|
288
|
+
|
|
289
|
+
### FontAwesome v5 (fa5)
|
|
290
|
+
|
|
291
|
+
```typescript
|
|
292
|
+
// Include FontAwesome v5 icons
|
|
293
|
+
[ipIconPack] = "['fa5']";
|
|
294
|
+
|
|
295
|
+
// Example icons
|
|
296
|
+
("fas fa-home");
|
|
297
|
+
("far fa-user");
|
|
298
|
+
("fab fa-angular");
|
|
299
|
+
("fal fa-cog");
|
|
300
|
+
("fad fa-heart");
|
|
301
|
+
```
|
|
302
|
+
|
|
303
|
+
### FontAwesome v6 (fa6)
|
|
304
|
+
|
|
305
|
+
```typescript
|
|
306
|
+
// Include FontAwesome v6 icons
|
|
307
|
+
[ipIconPack] = "['fa6']";
|
|
308
|
+
|
|
309
|
+
// Example icons
|
|
310
|
+
("fa-solid fa-home");
|
|
311
|
+
("fa-regular fa-user");
|
|
312
|
+
("fa-brands fa-angular");
|
|
313
|
+
("fa-light fa-cog");
|
|
314
|
+
("fa-duotone fa-heart");
|
|
315
|
+
```
|
|
316
|
+
|
|
317
|
+
### Material Icons (mat)
|
|
318
|
+
|
|
319
|
+
```typescript
|
|
320
|
+
// Include Material Icons
|
|
321
|
+
[ipIconPack] = "['mat']";
|
|
322
|
+
|
|
323
|
+
// Example icons
|
|
324
|
+
("mat home");
|
|
325
|
+
("mat person");
|
|
326
|
+
("mat settings");
|
|
327
|
+
("mat favorite");
|
|
328
|
+
("mat star");
|
|
329
|
+
```
|
|
330
|
+
|
|
331
|
+
### PrimeIcons (pi)
|
|
332
|
+
|
|
333
|
+
```typescript
|
|
334
|
+
// Include PrimeIcons
|
|
335
|
+
[ipIconPack] = "['pi']";
|
|
336
|
+
|
|
337
|
+
// Example icons
|
|
338
|
+
("pi pi-home");
|
|
339
|
+
("pi pi-user");
|
|
340
|
+
("pi pi-cog");
|
|
341
|
+
("pi pi-heart");
|
|
342
|
+
("pi pi-star");
|
|
343
|
+
```
|
|
344
|
+
|
|
345
|
+
### Multiple Icon Packs
|
|
346
|
+
|
|
347
|
+
```typescript
|
|
348
|
+
// Include all icon packs
|
|
349
|
+
[ipIconPack] = // Or use 'all' shorthand
|
|
350
|
+
"['fa', 'fa5', 'fa6', 'mat', 'pi']"[ipIconPack] = "['all']";
|
|
351
|
+
```
|
|
352
|
+
|
|
353
|
+
## 🔒 Security & Validation
|
|
354
|
+
|
|
355
|
+
### Icon Validation
|
|
356
|
+
|
|
357
|
+
```typescript
|
|
358
|
+
// Custom icon validation
|
|
359
|
+
validateIcon(icon: string): boolean {
|
|
360
|
+
// Check if icon exists in selected packs
|
|
361
|
+
const validIcons = this.getValidIcons();
|
|
362
|
+
return validIcons.some(validIcon => validIcon.name === icon);
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
// Sanitize icon input
|
|
366
|
+
sanitizeIcon(icon: string): string {
|
|
367
|
+
// Remove potentially dangerous characters
|
|
368
|
+
return icon.replace(/[<>\"'&]/g, '');
|
|
369
|
+
}
|
|
370
|
+
```
|
|
371
|
+
|
|
372
|
+
## 🧪 Testing
|
|
373
|
+
|
|
374
|
+
### Unit Tests
|
|
375
|
+
|
|
376
|
+
```typescript
|
|
377
|
+
import { ComponentFixture, TestBed } from "@angular/core/testing";
|
|
378
|
+
import { IconPickerModule } from "ngx-ntk-icon-picker";
|
|
379
|
+
|
|
380
|
+
describe("IconSelectorComponent", () => {
|
|
381
|
+
let component: IconSelectorComponent;
|
|
382
|
+
let fixture: ComponentFixture<IconSelectorComponent>;
|
|
383
|
+
|
|
384
|
+
beforeEach(async () => {
|
|
385
|
+
await TestBed.configureTestingModule({
|
|
386
|
+
imports: [IconPickerModule],
|
|
387
|
+
declarations: [IconSelectorComponent],
|
|
388
|
+
}).compileComponents();
|
|
389
|
+
|
|
390
|
+
fixture = TestBed.createComponent(IconSelectorComponent);
|
|
391
|
+
component = fixture.componentInstance;
|
|
392
|
+
fixture.detectChanges();
|
|
393
|
+
});
|
|
394
|
+
|
|
395
|
+
it("should create", () => {
|
|
396
|
+
expect(component).toBeTruthy();
|
|
397
|
+
});
|
|
398
|
+
|
|
399
|
+
it("should emit iconSelected event", () => {
|
|
400
|
+
const mockIcon = { name: "fa fa-home", id: "home", type: IconType.FontAwesome };
|
|
401
|
+
spyOn(component.iconSelected, "emit");
|
|
402
|
+
|
|
403
|
+
component.onIconSelected(mockIcon);
|
|
404
|
+
|
|
405
|
+
expect(component.iconSelected.emit).toHaveBeenCalledWith(mockIcon);
|
|
406
|
+
});
|
|
407
|
+
|
|
408
|
+
it("should filter icons by search term", () => {
|
|
409
|
+
component.search = "home";
|
|
410
|
+
const filteredIcons = component.getFilteredIcons();
|
|
411
|
+
|
|
412
|
+
expect(filteredIcons.every((icon) => icon.name.toLowerCase().includes("home") || icon.filter?.some((filter) => filter.toLowerCase().includes("home")))).toBe(true);
|
|
413
|
+
});
|
|
414
|
+
});
|
|
415
|
+
```
|
|
416
|
+
|
|
417
|
+
### Integration Tests
|
|
418
|
+
|
|
419
|
+
```typescript
|
|
420
|
+
import { ComponentFixture, TestBed } from "@angular/core/testing";
|
|
421
|
+
import { IconPickerModule } from "ngx-ntk-icon-picker";
|
|
422
|
+
|
|
423
|
+
describe("IconPicker Integration", () => {
|
|
424
|
+
let fixture: ComponentFixture<IconSelectorComponent>;
|
|
425
|
+
|
|
426
|
+
beforeEach(async () => {
|
|
427
|
+
await TestBed.configureTestingModule({
|
|
428
|
+
imports: [IconPickerModule],
|
|
429
|
+
declarations: [IconSelectorComponent],
|
|
430
|
+
}).compileComponents();
|
|
431
|
+
|
|
432
|
+
fixture = TestBed.createComponent(IconSelectorComponent);
|
|
433
|
+
});
|
|
434
|
+
|
|
435
|
+
it("should display selected icon", () => {
|
|
436
|
+
fixture.componentInstance.selectedIcon = "fa fa-home";
|
|
437
|
+
fixture.detectChanges();
|
|
438
|
+
|
|
439
|
+
const iconElement = fixture.nativeElement.querySelector(".selected-icon");
|
|
440
|
+
expect(iconElement).toBeTruthy();
|
|
441
|
+
expect(iconElement.textContent).toContain("home");
|
|
442
|
+
});
|
|
443
|
+
|
|
444
|
+
it("should open popup on button click", () => {
|
|
445
|
+
const button = fixture.nativeElement.querySelector(".icon-picker-button");
|
|
446
|
+
button.click();
|
|
447
|
+
fixture.detectChanges();
|
|
448
|
+
|
|
449
|
+
const popup = fixture.nativeElement.querySelector(".icon-picker-popup");
|
|
450
|
+
expect(popup).toBeTruthy();
|
|
451
|
+
});
|
|
452
|
+
|
|
453
|
+
it("should filter icons when searching", () => {
|
|
454
|
+
// Open popup
|
|
455
|
+
const button = fixture.nativeElement.querySelector(".icon-picker-button");
|
|
456
|
+
button.click();
|
|
457
|
+
fixture.detectChanges();
|
|
458
|
+
|
|
459
|
+
// Enter search term
|
|
460
|
+
const searchInput = fixture.nativeElement.querySelector(".search-input");
|
|
461
|
+
searchInput.value = "home";
|
|
462
|
+
searchInput.dispatchEvent(new Event("input"));
|
|
463
|
+
fixture.detectChanges();
|
|
464
|
+
|
|
465
|
+
// Check filtered results
|
|
466
|
+
const iconItems = fixture.nativeElement.querySelectorAll(".icon-item");
|
|
467
|
+
expect(iconItems.length).toBeGreaterThan(0);
|
|
468
|
+
});
|
|
469
|
+
});
|
|
470
|
+
```
|
|
471
|
+
|
|
472
|
+
## ⚡ Performance
|
|
473
|
+
|
|
474
|
+
### Optimization Tips
|
|
475
|
+
|
|
476
|
+
```typescript
|
|
477
|
+
// Use OnPush change detection for better performance
|
|
478
|
+
@Component({
|
|
479
|
+
selector: "app-icon-selector",
|
|
480
|
+
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
481
|
+
})
|
|
482
|
+
export class IconSelectorComponent {
|
|
483
|
+
// Implement trackBy function for ngFor
|
|
484
|
+
trackByIcon(index: number, icon: Icon): string {
|
|
485
|
+
return icon.id;
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
// Debounce search input
|
|
489
|
+
private searchSubject = new Subject<string>();
|
|
490
|
+
|
|
491
|
+
ngOnInit(): void {
|
|
492
|
+
this.searchSubject.pipe(debounceTime(300), distinctUntilChanged()).subscribe((searchTerm) => {
|
|
493
|
+
this.performSearch(searchTerm);
|
|
494
|
+
});
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
onSearchChange(searchTerm: string): void {
|
|
498
|
+
this.searchSubject.next(searchTerm);
|
|
499
|
+
}
|
|
500
|
+
}
|
|
501
|
+
```
|
|
502
|
+
|
|
503
|
+
### Memory Management
|
|
504
|
+
|
|
505
|
+
```typescript
|
|
506
|
+
// Proper cleanup
|
|
507
|
+
ngOnDestroy(): void {
|
|
508
|
+
this.searchSubject.complete();
|
|
509
|
+
this.destroy$.next();
|
|
510
|
+
this.destroy$.complete();
|
|
511
|
+
}
|
|
512
|
+
|
|
513
|
+
// Lazy load icon packs
|
|
514
|
+
loadIconPack(packName: string): void {
|
|
515
|
+
if (!this.loadedPacks.has(packName)) {
|
|
516
|
+
this.loadIconPackAsync(packName).subscribe(icons => {
|
|
517
|
+
this.loadedPacks.set(packName, icons);
|
|
518
|
+
});
|
|
519
|
+
}
|
|
520
|
+
}
|
|
521
|
+
```
|
|
522
|
+
|
|
523
|
+
## 📝 Examples
|
|
524
|
+
|
|
525
|
+
### Basic Icon Selector
|
|
526
|
+
|
|
527
|
+
```typescript
|
|
528
|
+
@Component({
|
|
529
|
+
selector: "app-basic-icon-selector",
|
|
530
|
+
template: `
|
|
531
|
+
<div class="icon-selector">
|
|
532
|
+
<label>Choose an icon:</label>
|
|
533
|
+
<icon-picker [(icon)]="selectedIcon" [ipIconPack]="['fa']" [ipPlaceHolder]="'Select an icon'" (iconSelected)="onIconSelected($event)"> </icon-picker>
|
|
534
|
+
</div>
|
|
535
|
+
`,
|
|
536
|
+
})
|
|
537
|
+
export class BasicIconSelectorComponent {
|
|
538
|
+
selectedIcon: string = "fa fa-star";
|
|
539
|
+
|
|
540
|
+
onIconSelected(icon: Icon): void {
|
|
541
|
+
console.log("Selected icon:", icon.name);
|
|
542
|
+
}
|
|
543
|
+
}
|
|
544
|
+
```
|
|
545
|
+
|
|
546
|
+
### Advanced Icon Selector with Custom Styling
|
|
547
|
+
|
|
548
|
+
```typescript
|
|
549
|
+
@Component({
|
|
550
|
+
selector: "app-advanced-icon-selector",
|
|
551
|
+
template: `
|
|
552
|
+
<div class="advanced-icon-selector">
|
|
553
|
+
<icon-picker [(icon)]="selectedIcon" [ipIconPack]="['fa', 'fa5', 'fa6', 'mat', 'pi']" [ipPosition]="'bottom'" [ipHeight]="400" [ipWidth]="350" [ipIconSize]="24" [ipIconVerticalPadding]="8" [ipIconHorizontalPadding]="8" [ipPlaceHolder]="'Choose an icon for your project'" [ipFallbackIcon]="'fa fa-question-circle'" [ipKeepSearchFilter]="true" [ipButtonStyleClass]="'custom-icon-button'" [ipInputSearchStyleClass]="'custom-search-input'" [ipDivSearchStyleClass]="'custom-search-container'" (iconSelected)="onIconSelected($event)"> </icon-picker>
|
|
554
|
+
</div>
|
|
555
|
+
`,
|
|
556
|
+
styles: [
|
|
557
|
+
`
|
|
558
|
+
.advanced-icon-selector {
|
|
559
|
+
.custom-icon-button {
|
|
560
|
+
display: flex;
|
|
561
|
+
align-items: center;
|
|
562
|
+
gap: 12px;
|
|
563
|
+
padding: 12px 16px;
|
|
564
|
+
border: 2px solid #e0e0e0;
|
|
565
|
+
border-radius: 8px;
|
|
566
|
+
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
|
567
|
+
color: white;
|
|
568
|
+
font-weight: 500;
|
|
569
|
+
cursor: pointer;
|
|
570
|
+
transition: all 0.3s ease;
|
|
571
|
+
|
|
572
|
+
&:hover {
|
|
573
|
+
border-color: #667eea;
|
|
574
|
+
transform: translateY(-2px);
|
|
575
|
+
box-shadow: 0 4px 12px rgba(102, 126, 234, 0.3);
|
|
576
|
+
}
|
|
577
|
+
}
|
|
578
|
+
|
|
579
|
+
.custom-search-input {
|
|
580
|
+
width: 100%;
|
|
581
|
+
padding: 12px 16px;
|
|
582
|
+
border: 2px solid #e0e0e0;
|
|
583
|
+
border-radius: 8px;
|
|
584
|
+
font-size: 16px;
|
|
585
|
+
background-color: #f8f9fa;
|
|
586
|
+
|
|
587
|
+
&:focus {
|
|
588
|
+
outline: none;
|
|
589
|
+
border-color: #667eea;
|
|
590
|
+
background-color: white;
|
|
591
|
+
box-shadow: 0 0 0 3px rgba(102, 126, 234, 0.1);
|
|
592
|
+
}
|
|
593
|
+
}
|
|
594
|
+
|
|
595
|
+
.custom-search-container {
|
|
596
|
+
background-color: #f8f9fa;
|
|
597
|
+
border-bottom: 2px solid #e0e0e0;
|
|
598
|
+
}
|
|
599
|
+
}
|
|
600
|
+
`,
|
|
601
|
+
],
|
|
602
|
+
})
|
|
603
|
+
export class AdvancedIconSelectorComponent {
|
|
604
|
+
selectedIcon: string = "fa fa-rocket";
|
|
605
|
+
|
|
606
|
+
onIconSelected(icon: Icon): void {
|
|
607
|
+
console.log("Advanced icon selected:", icon);
|
|
608
|
+
// Handle icon selection with additional logic
|
|
609
|
+
}
|
|
610
|
+
}
|
|
611
|
+
```
|
|
612
|
+
|
|
613
|
+
### Icon Selector with Validation
|
|
614
|
+
|
|
615
|
+
```typescript
|
|
616
|
+
@Component({
|
|
617
|
+
selector: "app-validated-icon-selector",
|
|
618
|
+
template: `
|
|
619
|
+
<div class="validated-icon-selector">
|
|
620
|
+
<icon-picker [(icon)]="selectedIcon" [ipIconPack]="['fa', 'fa5']" [ipPlaceHolder]="'Select a valid icon'" [ipFallbackIcon]="'fa fa-exclamation-triangle'" (iconSelected)="validateAndSelectIcon($event)"> </icon-picker>
|
|
621
|
+
|
|
622
|
+
<div *ngIf="validationError" class="validation-error">
|
|
623
|
+
{{ validationError }}
|
|
624
|
+
</div>
|
|
625
|
+
</div>
|
|
626
|
+
`,
|
|
627
|
+
})
|
|
628
|
+
export class ValidatedIconSelectorComponent {
|
|
629
|
+
selectedIcon: string = "";
|
|
630
|
+
validationError: string = "";
|
|
631
|
+
|
|
632
|
+
validateAndSelectIcon(icon: Icon): void {
|
|
633
|
+
// Custom validation logic
|
|
634
|
+
if (this.isIconAllowed(icon)) {
|
|
635
|
+
this.selectedIcon = icon.name;
|
|
636
|
+
this.validationError = "";
|
|
637
|
+
} else {
|
|
638
|
+
this.validationError = "This icon is not allowed for your account type.";
|
|
639
|
+
}
|
|
640
|
+
}
|
|
641
|
+
|
|
642
|
+
private isIconAllowed(icon: Icon): boolean {
|
|
643
|
+
// Implement your validation logic
|
|
644
|
+
const allowedIcons = ["fa fa-home", "fa fa-user", "fa fa-cog"];
|
|
645
|
+
return allowedIcons.includes(icon.name);
|
|
646
|
+
}
|
|
647
|
+
}
|
|
648
|
+
```
|
|
649
|
+
|
|
650
|
+
## 🔄 Version History
|
|
651
|
+
|
|
652
|
+
### v20.25.3
|
|
653
|
+
|
|
654
|
+
- Initial release with core functionality
|
|
655
|
+
- Multi-icon library support
|
|
656
|
+
- Search and filtering capabilities
|
|
657
|
+
- Customizable interface
|
|
658
|
+
- FontAwesome v4, v5, v6 integration
|
|
659
|
+
- Material Icons support
|
|
660
|
+
- PrimeIcons integration
|
|
661
|
+
|
|
662
|
+
### v20.25.2
|
|
663
|
+
|
|
664
|
+
- Bug fixes and performance improvements
|
|
665
|
+
- Enhanced search functionality
|
|
666
|
+
- Improved accessibility features
|
|
667
|
+
- Better mobile responsiveness
|
|
668
|
+
|
|
669
|
+
## 🤝 Contributing
|
|
670
|
+
|
|
671
|
+
We welcome contributions! Please see our contributing guidelines:
|
|
672
|
+
|
|
673
|
+
1. Fork the repository
|
|
674
|
+
2. Create a feature branch
|
|
675
|
+
3. Make your changes
|
|
676
|
+
4. Add tests for new functionality
|
|
677
|
+
5. Submit a pull request
|
|
678
|
+
|
|
679
|
+
## 📄 License
|
|
680
|
+
|
|
681
|
+
This project is licensed under the ISC License.
|
|
682
|
+
|
|
683
|
+
## 🆘 Support
|
|
684
|
+
|
|
685
|
+
For support and questions:
|
|
686
|
+
|
|
687
|
+
- Create an issue on GitHub
|
|
688
|
+
- Contact: [ntk.ir](https://ntk.ir)
|
|
689
|
+
|
|
690
|
+
## 👨💻 Author
|
|
691
|
+
|
|
692
|
+
**Alireza Karavi**
|
|
693
|
+
|
|
694
|
+
- GitHub: [@akaravi](https://github.com/akaravi)
|
|
695
|
+
- Website: [ntk.ir](https://ntk.ir)
|
|
60
696
|
|
|
61
|
-
|
|
697
|
+
---
|
|
62
698
|
|
|
63
|
-
|
|
699
|
+
**Note**: This library is part of the NTK CMS Angular Libraries collection. For more information, see the main project README.
|