diff --git a/live-editing/configs/ComboConfigGenerator.ts b/live-editing/configs/ComboConfigGenerator.ts
index 74e14111ee..716b5ed8d5 100644
--- a/live-editing/configs/ComboConfigGenerator.ts
+++ b/live-editing/configs/ComboConfigGenerator.ts
@@ -27,10 +27,76 @@ export class ComboConfigGenerator implements IConfigGenerator {
shortenComponentPathBy: '/lists/combo/'
}));
+ // combo overview
configs.push(new Config({
- additionalFiles: ['/src/app/data/heroData.ts', '/src/app/lists/combo/combo-styling/layout.scss'],
appConfig: BaseAppConfig,
- component: 'ComboStylingComponent'
+ component: 'ComboOverviewComponent',
+ shortenComponentPathBy: '/lists/combo/'
+ }));
+
+ // combo type
+ configs.push(new Config({
+ appConfig: BaseAppConfig,
+ component: 'ComboTypeComponent',
+ shortenComponentPathBy: '/lists/combo/'
+ }));
+
+ // combo disabled
+ configs.push(new Config({
+ appConfig: BaseAppConfig,
+ component: 'ComboDisabledComponent',
+ shortenComponentPathBy: '/lists/combo/'
+ }));
+
+ // combo size
+ configs.push(new Config({
+ appConfig: BaseAppConfig,
+ component: 'ComboSizeComponent',
+ shortenComponentPathBy: '/lists/combo/'
+ }));
+
+ // combo states
+ configs.push(new Config({
+ appConfig: BaseAppConfig,
+ component: 'ComboStatesComponent',
+ shortenComponentPathBy: '/lists/combo/'
+ }));
+
+ // combo validation
+ configs.push(new Config({
+ appConfig: BaseAppConfig,
+ component: 'ComboValidationComponent',
+ shortenComponentPathBy: '/lists/combo/'
+ }));
+
+ // combo dropdown size
+ configs.push(new Config({
+ appConfig: BaseAppConfig,
+ component: 'ComboDropdownSizeComponent',
+ shortenComponentPathBy: '/lists/combo/'
+ }));
+
+ // combo search input
+ configs.push(new Config({
+ appConfig: BaseAppConfig,
+ component: 'ComboSearchInputComponent',
+ shortenComponentPathBy: '/lists/combo/'
+ }));
+
+ // combo styling
+ configs.push(new Config({
+ appConfig: BaseAppConfig,
+ component: 'ComboStylingComponent',
+ shortenComponentPathBy: '/lists/combo/'
+ }));
+
+ // combo tailwind styling
+ configs.push(new Config({
+ additionalDependencies: ['tailwindcss', '@tailwindcss/postcss'],
+ additionalFiles: ['.postcssrc.json'],
+ appConfig: BaseAppConfig,
+ component: 'ComboTailwindStylingComponent',
+ shortenComponentPathBy: '/lists/combo/'
}));
configs.push(new Config({
@@ -72,17 +138,67 @@ export class ComboConfigGenerator implements IConfigGenerator {
shortenComponentPathBy: '/lists/combo/'
}));
+ // simple combo overview
configs.push(new Config({
- additionalFiles: ['/src/app/data/stocks-data.ts'],
- additionalDependencies: ['igniteui-angular-charts', 'igniteui-angular-core'],
appConfig: BaseAppConfig,
- component: 'SimpleComboMainComponent'
+ component: 'SimpleComboOverviewComponent',
+ shortenComponentPathBy: '/lists/combo/'
}));
+ // simple combo type
+ configs.push(new Config({
+ appConfig: BaseAppConfig,
+ component: 'SimpleComboTypeComponent',
+ shortenComponentPathBy: '/lists/combo/'
+ }));
+
+ // simple combo disabled
configs.push(new Config({
- additionalFiles: ['/src/app/data/cities15000-regions-countries.ts'],
appConfig: BaseAppConfig,
- component: 'SimpleComboUsageComponent'
+ component: 'SimpleComboDisabledComponent',
+ shortenComponentPathBy: '/lists/combo/'
+ }));
+
+ // simple combo size
+ configs.push(new Config({
+ appConfig: BaseAppConfig,
+ component: 'SimpleComboSizeComponent',
+ shortenComponentPathBy: '/lists/combo/'
+ }));
+
+ // simple combo states
+ configs.push(new Config({
+ appConfig: BaseAppConfig,
+ component: 'SimpleComboStatesComponent',
+ shortenComponentPathBy: '/lists/combo/'
+ }));
+
+ // simple combo validation
+ configs.push(new Config({
+ appConfig: BaseAppConfig,
+ component: 'SimpleComboValidationComponent',
+ shortenComponentPathBy: '/lists/combo/'
+ }));
+
+ // simple combo dropdown size
+ configs.push(new Config({
+ appConfig: BaseAppConfig,
+ component: 'SimpleComboDropdownSizeComponent',
+ shortenComponentPathBy: '/lists/combo/'
+ }));
+
+ // simple combo dropdown items
+ configs.push(new Config({
+ appConfig: BaseAppConfig,
+ component: 'SimpleComboDropdownItemsComponent',
+ shortenComponentPathBy: '/lists/combo/'
+ }));
+
+ // simple combo dropdown items states
+ configs.push(new Config({
+ appConfig: BaseAppConfig,
+ component: 'SimpleComboDropdownItemsStatesComponent',
+ shortenComponentPathBy: '/lists/combo/'
}));
configs.push(new Config({
@@ -97,10 +213,20 @@ export class ComboConfigGenerator implements IConfigGenerator {
component: 'SimpleComboRemoteComponent'
}));
+ // simple combo styling
configs.push(new Config({
- additionalFiles: ['/src/app/data/cities15000-regions-countries.ts', '/src/app/lists/combo/simple-combo-styling/layout.scss'],
appConfig: BaseAppConfig,
- component: 'SimpleComboStylingComponent'
+ component: 'SimpleComboStylingComponent',
+ shortenComponentPathBy: '/lists/combo/'
+ }));
+
+ // simple combo tailwind styling
+ configs.push(new Config({
+ additionalDependencies: ['tailwindcss', '@tailwindcss/postcss'],
+ additionalFiles: ['.postcssrc.json'],
+ appConfig: BaseAppConfig,
+ component: 'SimpleComboTailwindStylingComponent',
+ shortenComponentPathBy: '/lists/combo/'
}));
return configs;
diff --git a/src/app/lists/combo/combo-disabled/combo-disabled.component.html b/src/app/lists/combo/combo-disabled/combo-disabled.component.html
new file mode 100644
index 0000000000..a37714a5ae
--- /dev/null
+++ b/src/app/lists/combo/combo-disabled/combo-disabled.component.html
@@ -0,0 +1,24 @@
+
+
+
Enabled
+
Disabled
+ @for (row of rows; track $index) {
+ @let groupStart = $index === 2;
+
{{ row.label }}
+ @for (disabled of [false, true]; track disabled) {
+
+
+
+
+ }
+ }
+
diff --git a/src/app/lists/combo/combo-disabled/combo-disabled.component.scss b/src/app/lists/combo/combo-disabled/combo-disabled.component.scss
new file mode 100644
index 0000000000..267bee027b
--- /dev/null
+++ b/src/app/lists/combo/combo-disabled/combo-disabled.component.scss
@@ -0,0 +1,35 @@
+:host {
+ /* styles.scss asks the page for the large size; the combos keep their own
+ default medium size here. */
+ --ig-size: var(--ig-size-medium);
+
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ height: 100%;
+ padding: 1rem;
+}
+
+.states {
+ display: grid;
+ grid-template-columns: max-content repeat(2, minmax(0, 17.4375rem));
+ width: 100%;
+ justify-content: center;
+ align-items: center;
+ column-gap: 1.75rem;
+ row-gap: 1.5rem;
+}
+
+.group-start {
+ margin-top: 1.5rem;
+}
+
+.state-label {
+ font: 400 13px / 20px "Aktiv Grotesk", sans-serif;
+ letter-spacing: 0.3px;
+ color: var(--ig-gray-600);
+}
+
+.state-column {
+ justify-self: center;
+}
diff --git a/src/app/lists/combo/combo-disabled/combo-disabled.component.ts b/src/app/lists/combo/combo-disabled/combo-disabled.component.ts
new file mode 100644
index 0000000000..4a127857bb
--- /dev/null
+++ b/src/app/lists/combo/combo-disabled/combo-disabled.component.ts
@@ -0,0 +1,37 @@
+import { Component } from '@angular/core';
+import { FormsModule } from '@angular/forms';
+import { IgxComboComponent } from 'igniteui-angular/combo';
+import { IgxInputGroupType } from 'igniteui-angular/input-group';
+
+interface Country {
+ id: string;
+ name: string;
+ region: string;
+}
+
+interface DisabledRow {
+ label: string;
+ type: IgxInputGroupType;
+ value: string[];
+}
+
+@Component({
+ selector: 'app-combo-disabled',
+ styleUrls: ['./combo-disabled.component.scss'],
+ templateUrl: './combo-disabled.component.html',
+ imports: [IgxComboComponent, FormsModule]
+})
+export class ComboDisabledComponent {
+ public countries: Country[] = [
+ { id: 'BY', name: 'Belarus', region: 'Europe' },
+ { id: 'BE', name: 'Belgium', region: 'Europe' },
+ { id: 'BG', name: 'Bulgaria', region: 'Europe' }
+ ];
+
+ public rows: DisabledRow[] = [
+ { label: 'Idle', type: 'border', value: [] },
+ { label: 'Filled', type: 'border', value: ['BY', 'BE'] },
+ { label: 'Idle', type: 'box', value: [] },
+ { label: 'Filled', type: 'box', value: ['BY', 'BE'] }
+ ];
+}
diff --git a/src/app/lists/combo/combo-dropdown-size/combo-dropdown-size.component.html b/src/app/lists/combo/combo-dropdown-size/combo-dropdown-size.component.html
new file mode 100644
index 0000000000..49f28cd3d1
--- /dev/null
+++ b/src/app/lists/combo/combo-dropdown-size/combo-dropdown-size.component.html
@@ -0,0 +1,20 @@
+
+ @for (size of sizes; track size.label) {
+
+
{{ size.label }}
+
+
+
+
+
+
+ }
+
diff --git a/src/app/lists/combo/combo-dropdown-size/combo-dropdown-size.component.scss b/src/app/lists/combo/combo-dropdown-size/combo-dropdown-size.component.scss
new file mode 100644
index 0000000000..4a423025d5
--- /dev/null
+++ b/src/app/lists/combo/combo-dropdown-size/combo-dropdown-size.component.scss
@@ -0,0 +1,48 @@
+:host {
+ display: flex;
+ align-items: flex-start;
+ justify-content: center;
+ height: 100%;
+ padding: 2rem 1rem;
+}
+
+.sizes {
+ display: grid;
+ grid-template-columns: repeat(3, 11.25rem);
+ column-gap: 1rem;
+}
+
+.size {
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ gap: 1rem;
+}
+
+.dropdown {
+ width: 100%;
+}
+
+/* Only the dropdown of the combo is shown, so the input field is hidden. */
+.dropdown ::ng-deep igx-combo > igx-input-group {
+ display: none;
+}
+
+
+.small {
+ --ig-size: var(--ig-size-small);
+}
+
+.medium {
+ --ig-size: var(--ig-size-medium);
+}
+
+.large {
+ --ig-size: var(--ig-size-large);
+}
+
+.state-label {
+ font: 400 13px / 20px "Aktiv Grotesk", sans-serif;
+ letter-spacing: 0.3px;
+ color: var(--ig-gray-600);
+}
diff --git a/src/app/lists/combo/combo-dropdown-size/combo-dropdown-size.component.ts b/src/app/lists/combo/combo-dropdown-size/combo-dropdown-size.component.ts
new file mode 100644
index 0000000000..9dc4370bc7
--- /dev/null
+++ b/src/app/lists/combo/combo-dropdown-size/combo-dropdown-size.component.ts
@@ -0,0 +1,53 @@
+import { Component, ElementRef, afterNextRender, inject, viewChildren } from '@angular/core';
+import { IgxComboComponent } from 'igniteui-angular/combo';
+import { IBaseCancelableBrowserEventArgs } from 'igniteui-angular/core';
+import { forkJoin, take } from 'rxjs';
+
+interface Option {
+ id: number;
+ name: string;
+ group: string;
+}
+
+interface DropdownSize {
+ label: string;
+ className: string;
+}
+
+@Component({
+ selector: 'app-combo-dropdown-size',
+ styleUrls: ['./combo-dropdown-size.component.scss'],
+ templateUrl: './combo-dropdown-size.component.html',
+ imports: [IgxComboComponent]
+})
+export class ComboDropdownSizeComponent {
+ public options: Option[] = [1, 2, 3].map((id) => ({ id, name: 'Item', group: 'Header' }));
+
+ public sizes: DropdownSize[] = [
+ { label: 'Small', className: 'small' },
+ { label: 'Medium', className: 'medium' },
+ { label: 'Large', className: 'large' }
+ ];
+
+ private combos = viewChildren(IgxComboComponent);
+
+ constructor() {
+ const element = inject>(ElementRef).nativeElement;
+
+ // The combos are shown open. Opening a combo moves the focus to its search input, so
+ // the sample is inert until the combos open and the page keeps its focus.
+ afterNextRender(() => {
+ const combos = this.combos();
+ element.inert = true;
+ forkJoin(combos.map((combo) => combo.opened.pipe(take(1)))).subscribe(() => {
+ element.inert = false;
+ });
+ combos.forEach((combo) => combo.open());
+ });
+ }
+
+ // The input fields are hidden, so the dropdowns stay open and each one works on its own.
+ public keepOpen(event: IBaseCancelableBrowserEventArgs): void {
+ event.cancel = true;
+ }
+}
diff --git a/src/app/lists/combo/combo-overview/combo-overview.component.html b/src/app/lists/combo/combo-overview/combo-overview.component.html
new file mode 100644
index 0000000000..075b1e708d
--- /dev/null
+++ b/src/app/lists/combo/combo-overview/combo-overview.component.html
@@ -0,0 +1,14 @@
+
+
+
+
+
+
diff --git a/src/app/lists/combo/combo-overview/combo-overview.component.scss b/src/app/lists/combo/combo-overview/combo-overview.component.scss
new file mode 100644
index 0000000000..d38c5d64f5
--- /dev/null
+++ b/src/app/lists/combo/combo-overview/combo-overview.component.scss
@@ -0,0 +1,15 @@
+:host {
+ /* styles.scss asks the page for the large size; the combo keeps its own
+ default medium size here. */
+ --ig-size: var(--ig-size-medium);
+
+ display: flex;
+ align-items: flex-start;
+ justify-content: center;
+ height: 100%;
+ padding: 2rem 1rem;
+}
+
+.locations {
+ width: 22.5rem;
+}
diff --git a/src/app/lists/combo/combo-overview/combo-overview.component.ts b/src/app/lists/combo/combo-overview/combo-overview.component.ts
new file mode 100644
index 0000000000..de9f27f49d
--- /dev/null
+++ b/src/app/lists/combo/combo-overview/combo-overview.component.ts
@@ -0,0 +1,47 @@
+import { Component, ElementRef, afterNextRender, inject, viewChild } from '@angular/core';
+import { IgxComboComponent } from 'igniteui-angular/combo';
+import { IgxIconComponent, IgxIconService } from 'igniteui-angular/icon';
+import { IgxLabelDirective, IgxPrefixDirective } from 'igniteui-angular/input-group';
+import { take } from 'rxjs';
+
+const placeIcon = '';
+
+interface City {
+ id: string;
+ name: string;
+ country: string;
+}
+
+@Component({
+ selector: 'app-combo-overview',
+ styleUrls: ['./combo-overview.component.scss'],
+ templateUrl: './combo-overview.component.html',
+ imports: [IgxComboComponent, IgxLabelDirective, IgxPrefixDirective, IgxIconComponent]
+})
+export class ComboOverviewComponent {
+ public cities: City[] = [
+ { id: 'BG01', name: 'Sofia', country: 'Country of Bulgaria' },
+ { id: 'BG02', name: 'Plovdiv', country: 'Country of Bulgaria' },
+ { id: 'BG03', name: 'Varna', country: 'Country of Bulgaria' },
+ { id: 'BG04', name: 'Burgas', country: 'Country of Bulgaria' },
+ { id: 'BG05', name: 'Pleven', country: 'Country of Bulgaria' }
+ ];
+
+ private combo = viewChild.required(IgxComboComponent);
+
+ constructor() {
+ const element = inject>(ElementRef).nativeElement;
+ inject(IgxIconService).addSvgIconFromText('place', placeIcon, 'material');
+
+ // The combo is shown open. Opening it moves the focus to its search input, so the
+ // sample is inert until the combo opens and the page keeps its focus.
+ afterNextRender(() => {
+ const combo = this.combo();
+ element.inert = true;
+ combo.opened.pipe(take(1)).subscribe(() => {
+ element.inert = false;
+ });
+ combo.open();
+ });
+ }
+}
diff --git a/src/app/lists/combo/combo-search-input/combo-search-input.component.html b/src/app/lists/combo/combo-search-input/combo-search-input.component.html
new file mode 100644
index 0000000000..6d1a9d62cc
--- /dev/null
+++ b/src/app/lists/combo/combo-search-input/combo-search-input.component.html
@@ -0,0 +1,16 @@
+
+ @for (state of states; track state.label) {
+
+
{{ state.label }}
+
+
+
+
+
+
+ }
+
diff --git a/src/app/lists/combo/combo-search-input/combo-search-input.component.scss b/src/app/lists/combo/combo-search-input/combo-search-input.component.scss
new file mode 100644
index 0000000000..120955598e
--- /dev/null
+++ b/src/app/lists/combo/combo-search-input/combo-search-input.component.scss
@@ -0,0 +1,71 @@
+:host {
+ /* styles.scss asks the page for the large size; the combos keep their own
+ default medium size here. */
+ --ig-size: var(--ig-size-medium);
+
+ display: flex;
+ align-items: flex-start;
+ justify-content: center;
+ height: 100%;
+ padding: 2rem 1rem;
+}
+
+.states {
+ display: grid;
+ grid-template-columns: repeat(3, 11.25rem);
+ grid-template-rows: repeat(2, 5.5rem);
+ column-gap: 1rem;
+ row-gap: 1rem;
+}
+
+.state {
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ gap: 1rem;
+}
+
+.search {
+ width: 100%;
+ pointer-events: none;
+}
+
+/* Only the search input of the combo dropdown is shown. */
+.search ::ng-deep igx-combo > igx-input-group,
+.search ::ng-deep .igx-combo__add {
+ display: none;
+}
+
+/* The open dropdown takes no pointer input either. */
+.search ::ng-deep .igx-overlay__content {
+ pointer-events: none;
+}
+
+.search.hover ::ng-deep .igx-combo__search igx-input-group {
+ --idle-bottom-line-color: var(--hover-bottom-line-color);
+}
+
+.search.disabled ::ng-deep .igx-combo__search igx-input-group {
+ --idle-bottom-line-color: var(--disabled-bottom-line-color);
+ --placeholder-color: var(--disabled-placeholder-color);
+}
+
+.search.filled ::ng-deep .igx-combo__search igx-input-group {
+ --placeholder-color: var(--filled-text-color);
+}
+
+.search.focused ::ng-deep .igx-combo__search igx-input-group {
+ --idle-bottom-line-color: var(--focused-bottom-line-color);
+ --hover-bottom-line-color: var(--focused-bottom-line-color);
+}
+
+/* the input group shows its two pixels thick focused line */
+.search.focused ::ng-deep .igx-combo__search .igx-input-group__bundle::after {
+ scale: 1;
+}
+
+.state-label {
+ font: 400 13px / 20px "Aktiv Grotesk", sans-serif;
+ letter-spacing: 0.3px;
+ color: var(--ig-gray-600);
+}
diff --git a/src/app/lists/combo/combo-search-input/combo-search-input.component.ts b/src/app/lists/combo/combo-search-input/combo-search-input.component.ts
new file mode 100644
index 0000000000..56a2806670
--- /dev/null
+++ b/src/app/lists/combo/combo-search-input/combo-search-input.component.ts
@@ -0,0 +1,40 @@
+import { Component, ElementRef, afterNextRender, inject, viewChildren } from '@angular/core';
+import { IgxComboComponent } from 'igniteui-angular/combo';
+
+interface SearchState {
+ label: string;
+ className?: string;
+ text?: string;
+}
+
+@Component({
+ selector: 'app-combo-search-input',
+ styleUrls: ['./combo-search-input.component.scss'],
+ templateUrl: './combo-search-input.component.html',
+ imports: [IgxComboComponent]
+})
+export class ComboSearchInputComponent {
+ // The search input states come from the pointer and the keyboard, so the `hover`,
+ // `disabled`, `filled` and `focused` classes show them on the display-only combos.
+ public states: SearchState[] = [
+ { label: 'Idle' },
+ { label: 'Idle & Hover', className: 'hover' },
+ { label: 'Disabled', className: 'disabled' },
+ { label: 'Filled', className: 'filled', text: 'Input text' },
+ { label: 'Filled & Hover', className: 'filled hover', text: 'Input text' },
+ { label: 'Focused', className: 'filled focused', text: 'Input text' }
+ ];
+
+ private combos = viewChildren(IgxComboComponent);
+
+ constructor() {
+ // The search inputs are display-only, so the sample is inert. Opening a combo moves the
+ // focus to its search input, and the inert sample also keeps the focus on the page.
+ inject>(ElementRef).nativeElement.inert = true;
+
+ // The combos are shown open.
+ afterNextRender(() => {
+ this.combos().forEach((combo) => combo.open());
+ });
+ }
+}
diff --git a/src/app/lists/combo/combo-size/combo-size.component.html b/src/app/lists/combo/combo-size/combo-size.component.html
new file mode 100644
index 0000000000..7677e706e0
--- /dev/null
+++ b/src/app/lists/combo/combo-size/combo-size.component.html
@@ -0,0 +1,22 @@
+
+
+ @for (size of sizes; track size.label) {
+ {{ size.label }}
+ }
+ @for (type of types; track type.label) {
+ {{ type.label }}
+ @for (size of sizes; track size.label) {
+
+
+ }
+ }
+
diff --git a/src/app/lists/combo/combo-size/combo-size.component.scss b/src/app/lists/combo/combo-size/combo-size.component.scss
new file mode 100644
index 0000000000..a89cfd43a3
--- /dev/null
+++ b/src/app/lists/combo/combo-size/combo-size.component.scss
@@ -0,0 +1,44 @@
+:host {
+ display: flex;
+ align-items: flex-start;
+ justify-content: center;
+ height: 100%;
+ padding: 2rem 1rem;
+}
+
+.sizes {
+ display: grid;
+ grid-template-columns: max-content repeat(3, minmax(0, 17.4375rem));
+ width: 100%;
+ justify-content: center;
+ grid-template-rows: auto repeat(2, 17rem);
+ align-items: start;
+ column-gap: 1.75rem;
+ row-gap: 1rem;
+}
+
+.type-label {
+ padding-top: 0.75rem;
+}
+
+.small {
+ --ig-size: var(--ig-size-small);
+}
+
+.medium {
+ --ig-size: var(--ig-size-medium);
+}
+
+.large {
+ --ig-size: var(--ig-size-large);
+}
+
+.state-label {
+ font: 400 13px / 20px "Aktiv Grotesk", sans-serif;
+ letter-spacing: 0.3px;
+ color: var(--ig-gray-600);
+}
+
+.state-column {
+ justify-self: center;
+}
diff --git a/src/app/lists/combo/combo-size/combo-size.component.ts b/src/app/lists/combo/combo-size/combo-size.component.ts
new file mode 100644
index 0000000000..0a0c4af1fa
--- /dev/null
+++ b/src/app/lists/combo/combo-size/combo-size.component.ts
@@ -0,0 +1,62 @@
+import { Component, ElementRef, afterNextRender, inject, viewChildren } from '@angular/core';
+import { IgxComboComponent } from 'igniteui-angular/combo';
+import { IgxInputGroupType } from 'igniteui-angular/input-group';
+import { forkJoin, take } from 'rxjs';
+
+interface Country {
+ id: string;
+ name: string;
+ region: string;
+}
+
+interface ComboSize {
+ label: string;
+ className: string;
+}
+
+interface ComboType {
+ label: string;
+ type: IgxInputGroupType;
+}
+
+@Component({
+ selector: 'app-combo-size',
+ styleUrls: ['./combo-size.component.scss'],
+ templateUrl: './combo-size.component.html',
+ imports: [IgxComboComponent]
+})
+export class ComboSizeComponent {
+ public countries: Country[] = [
+ { id: 'BY', name: 'Belarus', region: 'Europe' },
+ { id: 'BE', name: 'Belgium', region: 'Europe' },
+ { id: 'BG', name: 'Bulgaria', region: 'Europe' }
+ ];
+
+ public sizes: ComboSize[] = [
+ { label: 'Small', className: 'small' },
+ { label: 'Medium', className: 'medium' },
+ { label: 'Large', className: 'large' }
+ ];
+
+ public types: ComboType[] = [
+ { label: 'Border Type', type: 'border' },
+ { label: 'Box Type', type: 'box' }
+ ];
+
+ private combos = viewChildren(IgxComboComponent);
+
+ constructor() {
+ const element = inject>(ElementRef).nativeElement;
+
+ // The combos are shown open. Opening a combo moves the focus to its search input, so
+ // the sample is inert until the combos open and the page keeps its focus.
+ afterNextRender(() => {
+ const combos = this.combos();
+ element.inert = true;
+ forkJoin(combos.map((combo) => combo.opened.pipe(take(1)))).subscribe(() => {
+ element.inert = false;
+ });
+ combos.forEach((combo) => combo.open());
+ });
+ }
+}
diff --git a/src/app/lists/combo/combo-states/combo-states.component.html b/src/app/lists/combo/combo-states/combo-states.component.html
new file mode 100644
index 0000000000..e67c8074b9
--- /dev/null
+++ b/src/app/lists/combo/combo-states/combo-states.component.html
@@ -0,0 +1,19 @@
+
+ @for (state of states; track state.label) {
+
+ {{ state.label }}
+
+
+
+ }
+
diff --git a/src/app/lists/combo/combo-states/combo-states.component.scss b/src/app/lists/combo/combo-states/combo-states.component.scss
new file mode 100644
index 0000000000..1e7cbd02a2
--- /dev/null
+++ b/src/app/lists/combo/combo-states/combo-states.component.scss
@@ -0,0 +1,69 @@
+:host {
+ /* styles.scss asks the page for the large size; the combos keep their own
+ default medium size here. */
+ --ig-size: var(--ig-size-medium);
+
+ display: flex;
+ align-items: flex-start;
+ justify-content: center;
+ height: 100%;
+ padding: 2rem 1rem;
+}
+
+.states {
+ display: grid;
+ grid-template-columns: repeat(3, minmax(0, 17.4375rem));
+ width: 100%;
+ justify-content: center;
+ column-gap: 1.75rem;
+ row-gap: 1.25rem;
+}
+
+.state {
+ display: flex;
+ flex-direction: column;
+ min-width: 0;
+ align-items: center;
+ gap: 0.5rem;
+}
+
+.state-combo {
+ width: 100%;
+ pointer-events: none;
+}
+
+/* The open dropdown takes no pointer input either. */
+.state-combo ::ng-deep .igx-overlay__content {
+ pointer-events: none;
+}
+
+/* The states belong to the combo's own input group, not to the search input in its dropdown. */
+.state-combo.hover > ::ng-deep igx-input-group {
+ --border-color: var(--hover-border-color);
+ --idle-bottom-line-color: var(--hover-bottom-line-color);
+}
+
+.state-combo.focused > ::ng-deep igx-input-group {
+ --border-color: var(--focused-border-color);
+ --idle-bottom-line-color: var(--focused-bottom-line-color);
+}
+
+.state-combo.focused > ::ng-deep igx-input-group .igx-input-group__bundle {
+ box-shadow: inset 0 0 0 0.125rem var(--focused-border-color);
+}
+
+.state-combo.focused > ::ng-deep igx-input-group .igx-input-group__toggle-button {
+ color: var(--toggle-button-foreground-focus);
+ background: var(--toggle-button-background-focus--border);
+}
+
+.state-combo.focused > ::ng-deep igx-input-group .igx-input-group__clear-button {
+ color: var(--clear-button-foreground-focus);
+ background: var(--clear-button-background-focus);
+}
+
+.state-label {
+ font: 400 13px / 20px "Aktiv Grotesk", sans-serif;
+ letter-spacing: 0.3px;
+ color: var(--ig-gray-600);
+}
diff --git a/src/app/lists/combo/combo-states/combo-states.component.ts b/src/app/lists/combo/combo-states/combo-states.component.ts
new file mode 100644
index 0000000000..6c2e8dc297
--- /dev/null
+++ b/src/app/lists/combo/combo-states/combo-states.component.ts
@@ -0,0 +1,60 @@
+import { Component, ElementRef, afterNextRender, inject, viewChildren } from '@angular/core';
+import { FormsModule } from '@angular/forms';
+import { IgxComboComponent } from 'igniteui-angular/combo';
+import { OverlaySettings } from 'igniteui-angular/core';
+
+interface Country {
+ id: string;
+ name: string;
+ region: string;
+}
+
+interface ComboState {
+ label: string;
+ className?: string;
+ value: string[];
+ open?: boolean;
+}
+
+@Component({
+ selector: 'app-combo-states',
+ styleUrls: ['./combo-states.component.scss'],
+ templateUrl: './combo-states.component.html',
+ imports: [IgxComboComponent, FormsModule]
+})
+export class ComboStatesComponent {
+ public countries: Country[] = [
+ { id: 'BY', name: 'Belarus', region: 'Europe' },
+ { id: 'BE', name: 'Belgium', region: 'Europe' },
+ { id: 'BG', name: 'Bulgaria', region: 'Europe' }
+ ];
+
+ // Hover and focus come from the pointer and the keyboard, so the `hover` and `focused`
+ // classes show them on the display-only combos.
+ public states: ComboState[] = [
+ { label: 'Idle', value: [] },
+ { label: 'Idle & Hover', className: 'hover', value: [] },
+ { label: 'Filled', value: ['BY', 'BE'] },
+ { label: 'Filled & Hover', className: 'hover', value: ['BY', 'BE'] },
+ { label: 'Focused', className: 'focused', value: ['BY', 'BE'] },
+ { label: 'Focused (Dropdown On)', className: 'focused', value: ['BY', 'BE'], open: true }
+ ];
+
+ // The open dropdown is display-only, so a click elsewhere does not close it.
+ public overlaySettings: OverlaySettings = { closeOnOutsideClick: false };
+
+ private combos = viewChildren(IgxComboComponent);
+
+ constructor() {
+ // The combos are display-only, so the sample is inert. Opening a combo moves the focus
+ // to its search input, and the inert sample also keeps the focus on the page.
+ inject>(ElementRef).nativeElement.inert = true;
+
+ // The last combo is shown with its dropdown open.
+ afterNextRender(() => {
+ this.combos()
+ .filter((_, index) => this.states[index].open)
+ .forEach((combo) => combo.open());
+ });
+ }
+}
diff --git a/src/app/lists/combo/combo-styling/combo-styling.component.html b/src/app/lists/combo/combo-styling/combo-styling.component.html
index 36303918d3..0b437c6d1e 100644
--- a/src/app/lists/combo/combo-styling/combo-styling.component.html
+++ b/src/app/lists/combo/combo-styling/combo-styling.component.html
@@ -1,10 +1,11 @@
-
-
-
-
-
-
-
-
+
+
diff --git a/src/app/lists/combo/combo-styling/combo-styling.component.scss b/src/app/lists/combo/combo-styling/combo-styling.component.scss
index 2348955c2f..6feace04af 100644
--- a/src/app/lists/combo/combo-styling/combo-styling.component.scss
+++ b/src/app/lists/combo/combo-styling/combo-styling.component.scss
@@ -1,23 +1,45 @@
-@use "layout.scss";
-@use "igniteui-angular/theming" as *;
+@use "sass:meta";
+@include meta.load-css("igniteui-angular/lib/core/styles/themes/presets/igniteui-bootstrap-light");
-$custom-drop-down-theme: drop-down-theme(
- $background-color: #57a5cd,
-);
+app-combo-styling {
+ /* styles.scss asks the page for the large size; the combo keeps its own
+ default medium size here. */
+ --ig-size: var(--ig-size-medium);
-$custom-combo-theme: combo-theme(
- $search-separator-border-color: #1d3743,
- $toggle-button-background: #57a5cd,
-);
+ display: flex;
+ align-items: flex-start;
+ justify-content: center;
+ height: 100%;
+ padding: 2rem 1rem;
+}
-$custom-checkbox-theme: checkbox-theme(
- $border-radius: 10px,
- $fill-color: #1d3743,
- $empty-color: #1d3743,
-);
+.styled-combo {
+ --ig-font-family: "Titillium Web", sans-serif;
+ --ig-input-group-border-color: #1377d5;
+ --ig-input-group-hover-border-color: #1377d5;
+ --ig-input-group-focused-border-color: #1377d5;
+ --ig-input-group-filled-text-color: #212121;
+ --ig-input-group-focused-bottom-line-color: #007ccf;
+ --ig-combo-toggle-button-background: #e0e0e0;
+ --ig-combo-toggle-button-background-focus: #e0e0e0;
+ --ig-combo-toggle-button-background-focus--border: #e0e0e0;
+ --ig-combo-toggle-button-foreground: #212121;
+ --ig-combo-toggle-button-foreground-focus: #212121;
+ --ig-combo-toggle-button-foreground-filled: #212121;
+ --ig-drop-down-background-color: #e6f2ff;
+ --ig-drop-down-header-text-color: #005dcc;
+ --ig-drop-down-item-text-color: #212121;
+ --ig-drop-down-hover-item-background: #cce4ff;
+ --ig-drop-down-focused-item-background: #cce4ff;
+ --ig-drop-down-selected-item-background: #4ca7e3;
+ --ig-drop-down-selected-item-text-color: #000;
+ --ig-drop-down-selected-hover-item-background: #4ca7e3;
+ --ig-drop-down-selected-hover-item-text-color: #000;
+ --ig-drop-down-selected-focus-item-background: #4ca7e3;
+ --ig-drop-down-selected-focus-item-text-color: #000;
+ --ig-checkbox-empty-color: #005dcc;
+ --ig-checkbox-fill-color: #005dcc;
+ --ig-checkbox-tick-color: #fff;
-:host {
- @include tokens($custom-drop-down-theme);
- @include tokens($custom-combo-theme);
- @include tokens($custom-checkbox-theme);
+ width: 17.4375rem;
}
diff --git a/src/app/lists/combo/combo-styling/combo-styling.component.ts b/src/app/lists/combo/combo-styling/combo-styling.component.ts
index 63ebea26f0..ee30b02a4e 100644
--- a/src/app/lists/combo/combo-styling/combo-styling.component.ts
+++ b/src/app/lists/combo/combo-styling/combo-styling.component.ts
@@ -1,34 +1,44 @@
-import { Component, ElementRef, OnInit, ViewChild, inject } from '@angular/core';
-import { IComboSelectionChangingEventArgs, IgxComboComponent } from 'igniteui-angular/combo';
-import { IgxToastComponent } from 'igniteui-angular/toast';
-import { VerticalAlignment } from 'igniteui-angular/core';
-import { getHeroWeaponsData, IHeroWeapon } from '../../../data/heroData';
+import { Component, ElementRef, ViewEncapsulation, afterNextRender, inject, viewChild } from '@angular/core';
+import { FormsModule } from '@angular/forms';
+import { IgxComboComponent } from 'igniteui-angular/combo';
+import { take } from 'rxjs';
+
+interface Country {
+ id: string;
+ name: string;
+ region: string;
+}
@Component({
- selector: 'app-combo',
+ selector: 'app-combo-styling',
+ encapsulation: ViewEncapsulation.None,
styleUrls: ['./combo-styling.component.scss'],
templateUrl: './combo-styling.component.html',
- imports: [IgxComboComponent, IgxToastComponent]
+ imports: [IgxComboComponent, FormsModule]
})
-export class ComboStylingComponent implements OnInit {
- elem = inject(ElementRef);
+export class ComboStylingComponent {
+ public countries: Country[] = [
+ { id: 'BY', name: 'Belarus', region: 'Europe' },
+ { id: 'BE', name: 'Belgium', region: 'Europe' },
+ { id: 'BG', name: 'Bulgaria', region: 'Europe' }
+ ];
- @ViewChild('loadToast', { read: IgxToastComponent, static: true })
- public loadToast: IgxToastComponent;
+ public selection = ['BY'];
- public weaponsData: IHeroWeapon[] = [];
-
- public ngOnInit() {
- this.weaponsData = getHeroWeaponsData();
- }
+ private combo = viewChild.required(IgxComboComponent);
- public setWeaponsLimit(event: IComboSelectionChangingEventArgs) {
- this.loadToast.positionSettings.verticalDirection = VerticalAlignment.Middle;
- // this.loadToast.autoHide = true;
+ constructor() {
+ const element = inject>(ElementRef).nativeElement;
- if (event.newValue.length > 2) {
- event.cancel = true;
- this.loadToast.open('Choose only two items');
- }
+ // The combo is shown open. Opening it moves the focus to its search input, so the
+ // sample is inert until the combo opens and the page keeps its focus.
+ afterNextRender(() => {
+ const combo = this.combo();
+ element.inert = true;
+ combo.opened.pipe(take(1)).subscribe(() => {
+ element.inert = false;
+ });
+ combo.open();
+ });
}
}
diff --git a/src/app/lists/combo/combo-styling/layout.scss b/src/app/lists/combo/combo-styling/layout.scss
deleted file mode 100644
index 82f1579d5d..0000000000
--- a/src/app/lists/combo/combo-styling/layout.scss
+++ /dev/null
@@ -1,3 +0,0 @@
-.control-wrapper {
- padding: 8px;
-}
\ No newline at end of file
diff --git a/src/app/lists/combo/combo-tailwind-styling/combo-tailwind-styling.component.html b/src/app/lists/combo/combo-tailwind-styling/combo-tailwind-styling.component.html
index 184c269959..259e0133ff 100644
--- a/src/app/lists/combo/combo-tailwind-styling/combo-tailwind-styling.component.html
+++ b/src/app/lists/combo/combo-tailwind-styling/combo-tailwind-styling.component.html
@@ -1,10 +1,11 @@
-
-
-
-
-
-
-
-
+
diff --git a/src/app/lists/combo/combo-tailwind-styling/combo-tailwind-styling.component.scss b/src/app/lists/combo/combo-tailwind-styling/combo-tailwind-styling.component.scss
index 99f78fbca7..69822a7ff4 100644
--- a/src/app/lists/combo/combo-tailwind-styling/combo-tailwind-styling.component.scss
+++ b/src/app/lists/combo/combo-tailwind-styling/combo-tailwind-styling.component.scss
@@ -1,3 +1,24 @@
-.control-wrapper {
- padding: 8px;
+@use "sass:meta";
+@import url("https://use.typekit.net/bng2zgw.css");
+@include meta.load-css("igniteui-angular/lib/core/styles/themes/presets/igniteui-bootstrap-light");
+
+app-combo-tailwind-styling {
+ /* The combo palette that the utilities in the template read. */
+ --color-combo-border: #a9b8cc;
+ --color-combo-toggle: #e5ebf3;
+ --color-combo-surface: #f3f6fa;
+ --color-combo-selected: #d2dbe8;
+ --color-combo-accent: #7d91ab;
+ --color-combo-header: #005dcc;
+ --color-combo-ink: #212121;
+
+ /* styles.scss asks the page for the large size; the combo keeps its own
+ default medium size here. */
+ --ig-size: var(--ig-size-medium);
+
+ display: flex;
+ align-items: flex-start;
+ justify-content: center;
+ height: 100%;
+ padding: 2rem 1rem;
}
diff --git a/src/app/lists/combo/combo-tailwind-styling/combo-tailwind-styling.component.ts b/src/app/lists/combo/combo-tailwind-styling/combo-tailwind-styling.component.ts
index 0bb9922864..35fc05d1f4 100644
--- a/src/app/lists/combo/combo-tailwind-styling/combo-tailwind-styling.component.ts
+++ b/src/app/lists/combo/combo-tailwind-styling/combo-tailwind-styling.component.ts
@@ -1,34 +1,44 @@
-import { Component, ElementRef, OnInit, ViewChild, inject } from '@angular/core';
-import { IComboSelectionChangingEventArgs, IgxComboComponent } from 'igniteui-angular/combo';
-import { IgxToastComponent } from 'igniteui-angular/toast';
-import { VerticalAlignment } from 'igniteui-angular/core';
-import { getHeroWeaponsData, IHeroWeapon } from '../../../data/heroData';
+import { Component, ElementRef, ViewEncapsulation, afterNextRender, inject, viewChild } from '@angular/core';
+import { FormsModule } from '@angular/forms';
+import { IgxComboComponent } from 'igniteui-angular/combo';
+import { take } from 'rxjs';
+
+interface Country {
+ id: string;
+ name: string;
+ region: string;
+}
@Component({
selector: 'app-combo-tailwind-styling',
+ encapsulation: ViewEncapsulation.None,
styleUrls: ['./combo-tailwind-styling.component.scss'],
templateUrl: './combo-tailwind-styling.component.html',
- imports: [IgxComboComponent, IgxToastComponent]
+ imports: [IgxComboComponent, FormsModule]
})
-export class ComboTailwindStylingComponent implements OnInit {
- elem = inject(ElementRef);
+export class ComboTailwindStylingComponent {
+ public countries: Country[] = [
+ { id: 'BY', name: 'Belarus', region: 'Europe' },
+ { id: 'BE', name: 'Belgium', region: 'Europe' },
+ { id: 'BG', name: 'Bulgaria', region: 'Europe' }
+ ];
- @ViewChild('loadToast', { read: IgxToastComponent, static: true })
- public loadToast: IgxToastComponent;
+ public selection = ['BY'];
- public weaponsData: IHeroWeapon[] = [];
-
- public ngOnInit() {
- this.weaponsData = getHeroWeaponsData();
- }
+ private combo = viewChild.required(IgxComboComponent);
- public setWeaponsLimit(event: IComboSelectionChangingEventArgs) {
- this.loadToast.positionSettings.verticalDirection = VerticalAlignment.Middle;
- // this.loadToast.autoHide = true;
+ constructor() {
+ const element = inject>(ElementRef).nativeElement;
- if (event.newValue.length > 2) {
- event.cancel = true;
- this.loadToast.open('Choose only two items');
- }
+ // The combo is shown open. Opening it moves the focus to its search input, so the
+ // sample is inert until the combo opens and the page keeps its focus.
+ afterNextRender(() => {
+ const combo = this.combo();
+ element.inert = true;
+ combo.opened.pipe(take(1)).subscribe(() => {
+ element.inert = false;
+ });
+ combo.open();
+ });
}
}
diff --git a/src/app/lists/combo/combo-template/combo-template.component.html b/src/app/lists/combo/combo-template/combo-template.component.html
index 0c0f50cb65..568828e30d 100644
--- a/src/app/lists/combo/combo-template/combo-template.component.html
+++ b/src/app/lists/combo/combo-template/combo-template.component.html
@@ -1,42 +1,103 @@
+
+
+ @if (templates.item) {
+
+
+
+ {{ destination[key] }} — {{ destination.country }} · {{ destination.id }} · {{ destination.offset }}
+
+
+ }
+ @if (templates.headerItem) {
+
+
+
+ {{ header[key] }}
+ {{ citiesIn(header[key]) }} cities
+
+
+ }
+ @if (templates.header) {
+
+
+
+ }
+ @if (templates.footer) {
+
+
+
+ }
+ @if (templates.empty) {
+
+
+
+ No city matches "{{ combo.searchValue }}". Add it below, or try another spelling.
+
+
+ }
+ @if (templates.add) {
+
+
+
+ }
+ @if (templates.toggleIcon) {
+
+
+
+ }
+ @if (templates.clearIcon) {
+
+
+
+ }
+ @if (templates.input) {
+
+ }
+ @if (templates.input) {
+
+ }
+ @if (templates.input) {
+
+ }
+ @if (templates.input) {
+ {{ combo.selection.length }} of {{ destinations.length }} destinations selected
+ }
+
-
-
- pin_drop
-
-
- {{ display[key] }} -
- {{ display.region }}
-
-
+
+ Selection
+ {{ selectedNames.length ? selectedNames.join(', ') : 'No items selected' }}
+
+
-
-
-
+
+ @for (template of templateSwitches; track template.name) {
+ {{ template.label }}
+ }
+
-
-
-
-
-
-
-
-
-
-
-
-
-
- {{ collapsed ? 'expand_more' : 'expand_less'}}
-
-
-
- cancel
-
-
-
- No available states
-
-
+
+
diff --git a/src/app/lists/combo/combo-template/combo-template.component.scss b/src/app/lists/combo/combo-template/combo-template.component.scss
index 52d3d93413..b1fb810603 100644
--- a/src/app/lists/combo/combo-template/combo-template.component.scss
+++ b/src/app/lists/combo/combo-template/combo-template.component.scss
@@ -1,18 +1,110 @@
-.combo {
- margin: 8px;
- width: 430px;
+:host {
+ /* styles.scss asks the page for the large size; the combo keeps its own
+ default medium size here. */
+ --ig-size: var(--ig-size-medium);
+
+ /* the combo keeps its width and stays in the center of a sample that has room for it: the
+ columns on its two sides share the free space equally, and the switches take the one after
+ the combo */
+ display: grid;
+ grid-template-columns: minmax(0, 1fr) minmax(0, 27rem) minmax(max-content, 1fr);
+ align-items: center;
+ height: 100%;
+ padding: 2rem 1rem;
+}
+
+/* the open dropdown covers the Selection list and ends below it, so the area has the height of
+ the combo with its dropdown, and the two stay in the middle of the sample */
+.destinations-area {
+ display: flex;
+ flex-direction: column;
+ grid-column: 2;
+ gap: 1.5rem;
+ height: 21.5rem;
+}
+
+.selection {
+ display: flex;
+ flex-direction: column;
+ gap: 0.25rem;
+ padding: 0 1.25rem;
+}
+
+.templates {
+ display: flex;
+ flex-direction: column;
+ grid-column: 3;
+ gap: 1.625rem;
+ justify-self: end;
+ margin-inline-start: 1.5rem;
+}
+
+.destination,
+.region {
+ display: inline-flex;
+ align-items: center;
+ gap: 0.5rem;
+}
+
+.flag {
+ width: 1.25rem;
+}
+
+.region {
+ width: 100%;
+}
+
+.region igx-icon {
+ --size: 1.125rem;
+}
+
+.region-count {
+ margin-inline-start: auto;
+ font-weight: 400;
+}
+
+.list-header,
+.list-footer {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ gap: 0.5rem;
+ padding: 0.25rem 0.5rem 0.25rem 1rem;
+}
+
+.empty-list {
+ display: flex;
+ align-items: flex-start;
+ gap: 0.5rem;
+ width: 100%;
+ text-align: start;
+}
+
+.empty-list igx-icon {
+ --size: 1.25rem;
+
+ flex: none;
+}
+
+.add-destination {
+ width: 100%;
+}
+
+.overlay-outlet {
+ position: absolute;
}
-.header-item {
- text-transform: capitalize;
+// the combo insets and centers its empty and add content; the design keeps them at the start of the list
+:host ::ng-deep .igx-combo__add {
+ align-items: stretch;
+ padding: 1rem 0.75rem;
}
-.header-class, .footer-class {
- text-align: center;
- padding: 4px;
- color: var(--ig-secondary-500);
+:host ::ng-deep .igx-combo__empty {
+ justify-content: flex-start;
+ padding: 0;
}
-.empty-class {
- margin: 8px;
+:host ::ng-deep .igx-combo__add-item .igx-button--flat {
+ margin-inline-start: -0.75rem;
}
diff --git a/src/app/lists/combo/combo-template/combo-template.component.ts b/src/app/lists/combo/combo-template/combo-template.component.ts
index aa9278e971..fd7ea91ec3 100644
--- a/src/app/lists/combo/combo-template/combo-template.component.ts
+++ b/src/app/lists/combo/combo-template/combo-template.component.ts
@@ -1,22 +1,102 @@
-import { ChangeDetectorRef, Component, OnInit, inject } from '@angular/core';
-import { localData } from './local-data';
-import { IgxComboAddItemDirective, IgxComboClearIconDirective, IgxComboComponent, IgxComboEmptyDirective, IgxComboFooterDirective, IgxComboHeaderDirective, IgxComboHeaderItemDirective, IgxComboItemDirective, IgxComboToggleIconDirective } from 'igniteui-angular/combo';
-import { IgxLabelDirective, IgxPrefixDirective } from 'igniteui-angular/input-group';
-import { IgxIconComponent } from 'igniteui-angular/icon';
+import { Component, ElementRef, afterNextRender, inject, viewChild } from '@angular/core';
+import { FormsModule } from '@angular/forms';
+import { IGX_COMBO_DIRECTIVES, IComboItemAdditionEvent, IgxComboComponent } from 'igniteui-angular/combo';
+import { IgxOverlayOutletDirective, SortingDirection } from 'igniteui-angular/core';
+import { IgxSwitchComponent } from 'igniteui-angular/switch';
+import { IgxIconComponent, IgxIconService } from 'igniteui-angular/icon';
+import { IgxBadgeComponent } from 'igniteui-angular/badge';
import { IgxButtonDirective } from 'igniteui-angular/directives';
+import { take } from 'rxjs';
+import { Destination, destinations } from './local-data';
+
+const icons: Record = {
+ place: '',
+ public: '',
+ flight: '',
+ close: '',
+ add: '',
+ location_searching: ''
+};
+
+type TemplateName = 'item' | 'headerItem' | 'header' | 'footer' | 'empty' | 'add' | 'toggleIcon' | 'clearIcon' | 'input';
+
+interface TemplateSwitch {
+ name: TemplateName;
+ label: string;
+}
@Component({
selector: 'app-combo-template',
styleUrls: ['./combo-template.component.scss'],
templateUrl: './combo-template.component.html',
- imports: [IgxComboComponent, IgxLabelDirective, IgxPrefixDirective, IgxIconComponent, IgxComboItemDirective, IgxComboHeaderItemDirective, IgxComboHeaderDirective, IgxComboFooterDirective, IgxComboAddItemDirective, IgxButtonDirective, IgxComboToggleIconDirective, IgxComboClearIconDirective, IgxComboEmptyDirective]
+ imports: [IGX_COMBO_DIRECTIVES, IgxSwitchComponent, IgxIconComponent, IgxBadgeComponent, IgxButtonDirective, IgxOverlayOutletDirective, FormsModule]
})
-export class ComboTemplateComponent implements OnInit {
- cdr = inject(ChangeDetectorRef);
+export class ComboTemplateComponent {
+ public readonly SortingDirection = SortingDirection;
+
+ public destinations: Destination[] = destinations;
+
+ public templateSwitches: TemplateSwitch[] = [
+ { name: 'item', label: 'Item template' },
+ { name: 'headerItem', label: 'Header Item template' },
+ { name: 'header', label: 'Header template' },
+ { name: 'footer', label: 'Footer template' },
+ { name: 'empty', label: 'Empty template' },
+ { name: 'add', label: 'Add template' },
+ { name: 'toggleIcon', label: 'Toggle Icon template' },
+ { name: 'clearIcon', label: 'Clear Icon template' },
+ { name: 'input', label: 'Templated input' }
+ ];
+
+ // which templates the switches have turned on
+ public templates: Record = {
+ item: false,
+ headerItem: false,
+ header: false,
+ footer: false,
+ empty: false,
+ add: false,
+ toggleIcon: false,
+ clearIcon: false,
+ input: false
+ };
+
+ // the `id` values of the selected destinations
+ public selected: string[] = [];
- public lData: any[];
+ private combo = viewChild.required(IgxComboComponent);
+
+ constructor() {
+ const element = inject>(ElementRef).nativeElement;
+ const iconService = inject(IgxIconService);
+
+ for (const [name, svg] of Object.entries(icons)) {
+ iconService.addSvgIconFromText(name, svg, 'material');
+ }
+
+ // The combo is shown open when the sample loads. Opening it moves the focus to its search
+ // input, so the sample is inert until the combo opens and the page keeps its focus.
+ afterNextRender(() => {
+ const combo = this.combo();
+ element.inert = true;
+ combo.opened.pipe(take(1)).subscribe(() => {
+ element.inert = false;
+ });
+ combo.open();
+ });
+ }
+
+ public get selectedNames(): string[] {
+ return this.destinations.filter((d) => this.selected.includes(d.id)).map((d) => d.name);
+ }
+
+ public citiesIn(region: string): number {
+ return this.destinations.filter((d) => d.region === region).length;
+ }
- public ngOnInit() {
- this.lData = localData;
+ // a custom value becomes a destination whose `id` and `name` hold the search text
+ public onAddition(event: IComboItemAdditionEvent): void {
+ event.addedItem.country = 'Custom destination';
+ event.addedItem.offset = '—';
}
}
diff --git a/src/app/lists/combo/combo-template/local-data.ts b/src/app/lists/combo/combo-template/local-data.ts
index a229ef33ae..c39a52e61e 100644
--- a/src/app/lists/combo/combo-template/local-data.ts
+++ b/src/app/lists/combo/combo-template/local-data.ts
@@ -1,34 +1,30 @@
-/* eslint-disable quote-props */
-/* eslint-disable @typescript-eslint/naming-convention */
-const division = {
- 'New England 01': ['Connecticut', 'Maine', 'Massachusetts'],
- 'New England 02': ['New Hampshire', 'Rhode Island', 'Vermont'],
- // tslint:disable-next-line:object-literal-sort-keys
- 'Mid-Atlantic': ['New Jersey', 'New York', 'Pennsylvania'],
- 'East North Central 02': ['Michigan', 'Ohio', 'Wisconsin'],
- 'East North Central 01': ['Illinois', 'Indiana'],
- 'West North Central 01': ['Missouri', 'Nebraska', 'North Dakota', 'South Dakota'],
- 'West North Central 02': ['Iowa', 'Kansas', 'Minnesota'],
- 'South Atlantic 01': ['Delaware', 'Florida', 'Georgia', 'Maryland'],
- 'South Atlantic 02': ['North Carolina', 'South Carolina', 'Virginia',
- 'District of Columbia', 'West Virginia'],
- 'East South Central 01': ['Alabama', 'Kentucky'],
- 'East South Central 02': ['Mississippi', 'Tennessee'],
- 'West South Central': ['Arkansas', 'Louisiana', 'Oklahome', 'Texas'],
- 'Mountain': ['Arizona', 'Colorado', 'Idaho', 'Montana', 'Nevada', 'New Mexico', 'Utah', 'Wyoming'],
- 'Pacific 01': ['Alaska', 'California'],
- 'Pacific 02': ['Hawaii', 'Oregon', 'Washington']
-};
-
-const localData: any[] = [];
-const keys = Object.keys(division);
-for (const key of keys) {
- division[key].map((e) => {
- localData.push({
- field: e,
- region: key.substring(0, key.length - 3)
- });
- });
+export interface Destination {
+ id: string;
+ name: string;
+ country: string;
+ countryCode: string;
+ region: string;
+ offset: string;
}
-export { localData };
+export const destinations: Destination[] = [
+ { id: 'SOF', name: 'Sofia', country: 'Bulgaria', countryCode: 'BG', region: 'Europe', offset: 'UTC+2' },
+ { id: 'LON', name: 'London', country: 'United Kingdom', countryCode: 'GB', region: 'Europe', offset: 'UTC+0' },
+ { id: 'PAR', name: 'Paris', country: 'France', countryCode: 'FR', region: 'Europe', offset: 'UTC+1' },
+ { id: 'BER', name: 'Berlin', country: 'Germany', countryCode: 'DE', region: 'Europe', offset: 'UTC+1' },
+ { id: 'MAD', name: 'Madrid', country: 'Spain', countryCode: 'ES', region: 'Europe', offset: 'UTC+1' },
+ { id: 'ROM', name: 'Rome', country: 'Italy', countryCode: 'IT', region: 'Europe', offset: 'UTC+1' },
+ { id: 'CPT', name: 'Cape Town', country: 'South Africa', countryCode: 'ZA', region: 'Africa', offset: 'UTC+2' },
+ { id: 'NBO', name: 'Nairobi', country: 'Kenya', countryCode: 'KE', region: 'Africa', offset: 'UTC+3' },
+ { id: 'CAI', name: 'Cairo', country: 'Egypt', countryCode: 'EG', region: 'Africa', offset: 'UTC+2' },
+ { id: 'RAK', name: 'Marrakesh', country: 'Morocco', countryCode: 'MA', region: 'Africa', offset: 'UTC+1' },
+ { id: 'TYO', name: 'Tokyo', country: 'Japan', countryCode: 'JP', region: 'Asia', offset: 'UTC+9' },
+ { id: 'SIN', name: 'Singapore', country: 'Singapore', countryCode: 'SG', region: 'Asia', offset: 'UTC+8' },
+ { id: 'BKK', name: 'Bangkok', country: 'Thailand', countryCode: 'TH', region: 'Asia', offset: 'UTC+7' },
+ { id: 'DXB', name: 'Dubai', country: 'United Arab Emirates', countryCode: 'AE', region: 'Asia', offset: 'UTC+4' },
+ { id: 'NYC', name: 'New York', country: 'United States', countryCode: 'US', region: 'Americas', offset: 'UTC-5' },
+ { id: 'YTO', name: 'Toronto', country: 'Canada', countryCode: 'CA', region: 'Americas', offset: 'UTC-5' },
+ { id: 'MEX', name: 'Mexico City', country: 'Mexico', countryCode: 'MX', region: 'Americas', offset: 'UTC-6' },
+ { id: 'SAO', name: 'São Paulo', country: 'Brazil', countryCode: 'BR', region: 'Americas', offset: 'UTC-3' },
+ { id: 'BUE', name: 'Buenos Aires', country: 'Argentina', countryCode: 'AR', region: 'Americas', offset: 'UTC-3' }
+];
diff --git a/src/app/lists/combo/combo-type/combo-type.component.html b/src/app/lists/combo/combo-type/combo-type.component.html
new file mode 100644
index 0000000000..5242e11b6c
--- /dev/null
+++ b/src/app/lists/combo/combo-type/combo-type.component.html
@@ -0,0 +1,21 @@
+
+ @for (type of types; track type.label) {
+
+ {{ type.label }}
+
+
+
+
+
+
+
+ }
+
diff --git a/src/app/lists/combo/combo-type/combo-type.component.scss b/src/app/lists/combo/combo-type/combo-type.component.scss
new file mode 100644
index 0000000000..f45cb9a3d9
--- /dev/null
+++ b/src/app/lists/combo/combo-type/combo-type.component.scss
@@ -0,0 +1,34 @@
+:host {
+ /* styles.scss asks the page for the large size; the combos keep their own
+ default medium size here. */
+ --ig-size: var(--ig-size-medium);
+
+ display: flex;
+ align-items: flex-start;
+ justify-content: center;
+ height: 100%;
+ padding: 2rem 1rem;
+}
+
+.types {
+ display: grid;
+ grid-template-columns: repeat(2, minmax(0, 22.5rem));
+ width: 100%;
+ justify-content: center;
+ column-gap: 3.75rem;
+}
+
+.type {
+ display: flex;
+ flex-direction: column;
+ min-width: 0;
+ gap: 0.75rem;
+}
+
+.type-label {
+ justify-self: center;
+ text-align: center;
+ font: 400 13px / 20px "Aktiv Grotesk", sans-serif;
+ letter-spacing: 0.3px;
+ color: var(--ig-gray-600);
+}
diff --git a/src/app/lists/combo/combo-type/combo-type.component.ts b/src/app/lists/combo/combo-type/combo-type.component.ts
new file mode 100644
index 0000000000..dbe0a06be9
--- /dev/null
+++ b/src/app/lists/combo/combo-type/combo-type.component.ts
@@ -0,0 +1,51 @@
+import { Component, ElementRef, afterNextRender, inject, viewChildren } from '@angular/core';
+import { IgxComboComponent } from 'igniteui-angular/combo';
+import { IgxIconComponent, IgxIconService } from 'igniteui-angular/icon';
+import { IgxInputGroupType, IgxLabelDirective, IgxPrefixDirective } from 'igniteui-angular/input-group';
+import { forkJoin, take } from 'rxjs';
+
+const languageIcon = '';
+
+interface Option {
+ id: number;
+ name: string;
+ group: string;
+}
+
+interface ComboType {
+ label: string;
+ type: IgxInputGroupType;
+}
+
+@Component({
+ selector: 'app-combo-type',
+ styleUrls: ['./combo-type.component.scss'],
+ templateUrl: './combo-type.component.html',
+ imports: [IgxComboComponent, IgxLabelDirective, IgxPrefixDirective, IgxIconComponent]
+})
+export class ComboTypeComponent {
+ public options: Option[] = [1, 2, 3, 4, 5].map((id) => ({ id, name: 'Item', group: 'Header' }));
+
+ public types: ComboType[] = [
+ { label: 'Border Type', type: 'border' },
+ { label: 'Box Type', type: 'box' }
+ ];
+
+ private combos = viewChildren(IgxComboComponent);
+
+ constructor() {
+ const element = inject>(ElementRef).nativeElement;
+ inject(IgxIconService).addSvgIconFromText('language', languageIcon, 'material');
+
+ // The combos are shown open. Opening a combo moves the focus to its search input, so
+ // the sample is inert until the combos open and the page keeps its focus.
+ afterNextRender(() => {
+ const combos = this.combos();
+ element.inert = true;
+ forkJoin(combos.map((combo) => combo.opened.pipe(take(1)))).subscribe(() => {
+ element.inert = false;
+ });
+ combos.forEach((combo) => combo.open());
+ });
+ }
+}
diff --git a/src/app/lists/combo/combo-validation/combo-validation.component.html b/src/app/lists/combo/combo-validation/combo-validation.component.html
new file mode 100644
index 0000000000..c81767a8f2
--- /dev/null
+++ b/src/app/lists/combo/combo-validation/combo-validation.component.html
@@ -0,0 +1,24 @@
+
+
+ @for (state of states; track state.label) {
+ {{ state.label }}
+ }
+ @for (type of types; track type.label) {
+ {{ type.label }}
+ @for (state of states; track state.label) {
+
+ @if (state.helperText) {
+ {{ state.helperText }}
+ }
+
+ }
+ }
+
diff --git a/src/app/lists/combo/combo-validation/combo-validation.component.scss b/src/app/lists/combo/combo-validation/combo-validation.component.scss
new file mode 100644
index 0000000000..f9f40dcc31
--- /dev/null
+++ b/src/app/lists/combo/combo-validation/combo-validation.component.scss
@@ -0,0 +1,56 @@
+:host {
+ /* styles.scss asks the page for the large size; the combos keep their own
+ default medium size here. */
+ --ig-size: var(--ig-size-medium);
+
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ height: 100%;
+ padding: 1rem;
+}
+
+.states {
+ display: grid;
+ grid-template-columns: max-content repeat(3, minmax(0, 17.4375rem));
+ width: 100%;
+ justify-content: center;
+ align-items: start;
+ column-gap: 1.75rem;
+ row-gap: 1rem;
+}
+
+.type-label {
+ padding-top: 0.75rem;
+}
+
+.state-combo {
+ pointer-events: none;
+}
+
+/* The Material theme leaves the bottom line of a valid box combo uncolored, so the
+ success combos color it with the success color. */
+@container style(--ig-theme: material) {
+ .state-combo.success > ::ng-deep igx-input-group .igx-input-group__bundle::before {
+ border-block-end-color: var(--success-secondary-color);
+ }
+}
+
+.state-combo.warn > ::ng-deep igx-input-group {
+ --border-color: var(--warning-secondary-color);
+ --idle-bottom-line-color: var(--warning-secondary-color);
+}
+
+.state-combo.warn igx-hint {
+ color: var(--warning-secondary-color);
+}
+
+.state-label {
+ font: 400 13px / 20px "Aktiv Grotesk", sans-serif;
+ letter-spacing: 0.3px;
+ color: var(--ig-gray-600);
+}
+
+.state-column {
+ justify-self: center;
+}
diff --git a/src/app/lists/combo/combo-validation/combo-validation.component.ts b/src/app/lists/combo/combo-validation/combo-validation.component.ts
new file mode 100644
index 0000000000..d182430dd1
--- /dev/null
+++ b/src/app/lists/combo/combo-validation/combo-validation.component.ts
@@ -0,0 +1,70 @@
+import { ChangeDetectorRef, Component, ElementRef, afterNextRender, inject, viewChildren } from '@angular/core';
+import { IgxComboComponent } from 'igniteui-angular/combo';
+import { IgxHintDirective, IgxInputGroupType, IgxInputState } from 'igniteui-angular/input-group';
+
+interface Country {
+ id: string;
+ name: string;
+ region: string;
+}
+
+interface ValidationState {
+ label: string;
+ className: string;
+ state?: IgxInputState;
+ helperText?: string;
+}
+
+interface ComboType {
+ label: string;
+ type: IgxInputGroupType;
+}
+
+@Component({
+ selector: 'app-combo-validation',
+ styleUrls: ['./combo-validation.component.scss'],
+ templateUrl: './combo-validation.component.html',
+ imports: [IgxComboComponent, IgxHintDirective]
+})
+export class ComboValidationComponent {
+ public countries: Country[] = [
+ { id: 'BY', name: 'Belarus', region: 'Europe' },
+ { id: 'BE', name: 'Belgium', region: 'Europe' },
+ { id: 'BG', name: 'Bulgaria', region: 'Europe' }
+ ];
+
+ // The combo has built-in valid and invalid states, so the `warn` class shows the
+ // warning state on the display-only combos.
+ public states: ValidationState[] = [
+ { label: 'Success', className: 'success', state: IgxInputState.VALID },
+ { label: 'Warn', className: 'warn', helperText: 'Additional fees might be included' },
+ { label: 'Error', className: 'error', state: IgxInputState.INVALID, helperText: 'Please select a different region' }
+ ];
+
+ public types: ComboType[] = [
+ { label: 'Border Type', type: 'border' },
+ { label: 'Box Type', type: 'box' }
+ ];
+
+ private combos = viewChildren(IgxComboComponent);
+ private cdr = inject(ChangeDetectorRef);
+
+ constructor() {
+ // The combos are display-only, so the sample is inert.
+ inject>(ElementRef).nativeElement.inert = true;
+
+ afterNextRender(() => {
+ for (const combo of this.combos()) {
+ combo.select(['BY', 'BE']);
+ }
+ this.setStates();
+ });
+ }
+
+ private setStates() {
+ this.combos().forEach((combo, index) => {
+ combo.valid = this.states[index % this.states.length].state ?? IgxInputState.INITIAL;
+ });
+ this.cdr.markForCheck();
+ }
+}
diff --git a/src/app/lists/combo/simple-combo-disabled/simple-combo-disabled.component.html b/src/app/lists/combo/simple-combo-disabled/simple-combo-disabled.component.html
new file mode 100644
index 0000000000..32223685b7
--- /dev/null
+++ b/src/app/lists/combo/simple-combo-disabled/simple-combo-disabled.component.html
@@ -0,0 +1,23 @@
+
+
+
Enabled
+
Disabled
+ @for (row of rows; track $index) {
+ @let groupStart = $index === 2;
+
{{ row.label }}
+ @for (disabled of [false, true]; track disabled) {
+
+
+
+
+ }
+ }
+
diff --git a/src/app/lists/combo/simple-combo-disabled/simple-combo-disabled.component.scss b/src/app/lists/combo/simple-combo-disabled/simple-combo-disabled.component.scss
new file mode 100644
index 0000000000..267bee027b
--- /dev/null
+++ b/src/app/lists/combo/simple-combo-disabled/simple-combo-disabled.component.scss
@@ -0,0 +1,35 @@
+:host {
+ /* styles.scss asks the page for the large size; the combos keep their own
+ default medium size here. */
+ --ig-size: var(--ig-size-medium);
+
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ height: 100%;
+ padding: 1rem;
+}
+
+.states {
+ display: grid;
+ grid-template-columns: max-content repeat(2, minmax(0, 17.4375rem));
+ width: 100%;
+ justify-content: center;
+ align-items: center;
+ column-gap: 1.75rem;
+ row-gap: 1.5rem;
+}
+
+.group-start {
+ margin-top: 1.5rem;
+}
+
+.state-label {
+ font: 400 13px / 20px "Aktiv Grotesk", sans-serif;
+ letter-spacing: 0.3px;
+ color: var(--ig-gray-600);
+}
+
+.state-column {
+ justify-self: center;
+}
diff --git a/src/app/lists/combo/simple-combo-disabled/simple-combo-disabled.component.ts b/src/app/lists/combo/simple-combo-disabled/simple-combo-disabled.component.ts
new file mode 100644
index 0000000000..b65ac52166
--- /dev/null
+++ b/src/app/lists/combo/simple-combo-disabled/simple-combo-disabled.component.ts
@@ -0,0 +1,37 @@
+import { Component } from '@angular/core';
+import { FormsModule } from '@angular/forms';
+import { IgxSimpleComboComponent } from 'igniteui-angular/simple-combo';
+import { IgxInputGroupType } from 'igniteui-angular/input-group';
+
+interface Country {
+ id: string;
+ name: string;
+ region: string;
+}
+
+interface DisabledRow {
+ label: string;
+ type: IgxInputGroupType;
+ value?: string;
+}
+
+@Component({
+ selector: 'app-simple-combo-disabled',
+ styleUrls: ['./simple-combo-disabled.component.scss'],
+ templateUrl: './simple-combo-disabled.component.html',
+ imports: [IgxSimpleComboComponent, FormsModule]
+})
+export class SimpleComboDisabledComponent {
+ public countries: Country[] = [
+ { id: 'BY', name: 'Belarus', region: 'Europe' },
+ { id: 'BE', name: 'Belgium', region: 'Europe' },
+ { id: 'BG', name: 'Bulgaria', region: 'Europe' }
+ ];
+
+ public rows: DisabledRow[] = [
+ { label: 'Idle', type: 'border', value: undefined },
+ { label: 'Filled', type: 'border', value: 'BY' },
+ { label: 'Idle', type: 'box', value: undefined },
+ { label: 'Filled', type: 'box', value: 'BY' }
+ ];
+}
diff --git a/src/app/lists/combo/simple-combo-dropdown-items-states/simple-combo-dropdown-items-states.component.html b/src/app/lists/combo/simple-combo-dropdown-items-states/simple-combo-dropdown-items-states.component.html
new file mode 100644
index 0000000000..6e2a68341a
--- /dev/null
+++ b/src/app/lists/combo/simple-combo-dropdown-items-states/simple-combo-dropdown-items-states.component.html
@@ -0,0 +1,24 @@
+
+
+
+ @for (state of states; track state.label) {
+
{{ state.label }}
+ }
+ @for (row of rows; track row.label) {
+
{{ row.label }}
+ @for (state of row.states; track state.label) {
+
+
+
+
+
+ }
+ }
+
diff --git a/src/app/lists/combo/simple-combo-dropdown-items-states/simple-combo-dropdown-items-states.component.scss b/src/app/lists/combo/simple-combo-dropdown-items-states/simple-combo-dropdown-items-states.component.scss
new file mode 100644
index 0000000000..d60697af04
--- /dev/null
+++ b/src/app/lists/combo/simple-combo-dropdown-items-states/simple-combo-dropdown-items-states.component.scss
@@ -0,0 +1,79 @@
+:host {
+ /* styles.scss asks the page for the large size; the items keep their own
+ default medium size here. */
+ --ig-size: var(--ig-size-medium);
+
+ display: flex;
+ align-items: flex-start;
+ justify-content: center;
+ height: 100%;
+ padding: 2rem 1rem;
+}
+
+.items {
+ display: grid;
+ grid-template-columns: max-content repeat(4, minmax(0, 11.25rem));
+ width: 100%;
+ justify-content: center;
+ align-items: center;
+ column-gap: 1.875rem;
+ row-gap: 1rem;
+}
+
+/* The second row is further from the first one than the column labels are. */
+.items > :nth-child(n + 11) {
+ margin-top: 2rem;
+}
+
+/* The list floats, so the element takes the height of its one medium item. */
+.dropdown {
+ width: 100%;
+ min-width: 0;
+ height: 2rem;
+ pointer-events: none;
+}
+
+/* Only the dropdown of the combo is shown, so the input field is hidden. */
+.dropdown ::ng-deep igx-simple-combo > igx-input-group {
+ display: none;
+}
+
+/* The states use the colors of the drop-down theme. */
+.dropdown.hover ::ng-deep .igx-drop-down__item {
+ background: var(--hover-item-background);
+ color: var(--hover-item-text-color);
+}
+
+.dropdown.focus ::ng-deep .igx-drop-down__item {
+ background: var(--focused-item-background);
+ color: var(--focused-item-text-color);
+}
+
+.dropdown.disabled ::ng-deep .igx-drop-down__item {
+ background: var(--disabled-item-background);
+ color: var(--disabled-item-text-color);
+}
+
+.dropdown.hover ::ng-deep .igx-drop-down__item--selected {
+ background: var(--selected-hover-item-background);
+ color: var(--selected-hover-item-text-color);
+}
+
+.dropdown.focus ::ng-deep .igx-drop-down__item--selected {
+ background: var(--selected-focus-item-background);
+ color: var(--selected-focus-item-text-color);
+}
+
+.state-label {
+ font: 400 13px / 20px "Aktiv Grotesk", sans-serif;
+ letter-spacing: 0.3px;
+ color: var(--ig-gray-600);
+}
+
+.state-label:not(.state-column) {
+ justify-self: end;
+}
+
+.state-column {
+ justify-self: center;
+}
diff --git a/src/app/lists/combo/simple-combo-dropdown-items-states/simple-combo-dropdown-items-states.component.ts b/src/app/lists/combo/simple-combo-dropdown-items-states/simple-combo-dropdown-items-states.component.ts
new file mode 100644
index 0000000000..186e2d8a7f
--- /dev/null
+++ b/src/app/lists/combo/simple-combo-dropdown-items-states/simple-combo-dropdown-items-states.component.ts
@@ -0,0 +1,64 @@
+import { Component, afterNextRender, viewChildren } from '@angular/core';
+import { FormsModule } from '@angular/forms';
+import { ConnectedPositioningStrategy, IBaseCancelableBrowserEventArgs, VerticalAlignment } from 'igniteui-angular/core';
+import { IgxSimpleComboComponent } from 'igniteui-angular/simple-combo';
+
+interface Option {
+ id: number;
+ name: string;
+}
+
+interface ItemState {
+ label: string;
+ className?: string;
+}
+
+interface ItemRow {
+ label: string;
+ value?: number;
+ states: ItemState[];
+}
+
+@Component({
+ selector: 'app-simple-combo-dropdown-items-states',
+ styleUrls: ['./simple-combo-dropdown-items-states.component.scss'],
+ templateUrl: './simple-combo-dropdown-items-states.component.html',
+ imports: [IgxSimpleComboComponent, FormsModule]
+})
+export class SimpleComboDropdownItemsStatesComponent {
+ // The dropdown opens at the top of the element around the combo, where the hidden input field is.
+ public positionStrategy = new ConnectedPositioningStrategy({
+ verticalStartPoint: VerticalAlignment.Top,
+ verticalDirection: VerticalAlignment.Bottom
+ });
+
+ public options: Option[] = [{ id: 1, name: 'Item' }];
+
+ // Hover, focus, and disabled come from the pointer, the keyboard, and the data, so the
+ // `hover`, `focus`, and `disabled` classes show them on the display-only items.
+ public states: ItemState[] = [
+ { label: 'Idle' },
+ { label: 'Hover', className: 'hover' },
+ { label: 'Focus', className: 'focus' },
+ { label: 'Disabled', className: 'disabled' }
+ ];
+
+ public rows: ItemRow[] = [
+ { label: 'Unselected', states: this.states },
+ { label: 'Selected', value: 1, states: this.states.slice(0, 3) }
+ ];
+
+ private combos = viewChildren(IgxSimpleComboComponent);
+
+ constructor() {
+ // The dropdowns are shown open.
+ afterNextRender(() => {
+ this.combos().forEach((combo) => combo.open());
+ });
+ }
+
+ // The input fields are hidden, so the dropdowns stay open.
+ public keepOpen(event: IBaseCancelableBrowserEventArgs): void {
+ event.cancel = true;
+ }
+}
diff --git a/src/app/lists/combo/simple-combo-dropdown-items/simple-combo-dropdown-items.component.html b/src/app/lists/combo/simple-combo-dropdown-items/simple-combo-dropdown-items.component.html
new file mode 100644
index 0000000000..372ab527f5
--- /dev/null
+++ b/src/app/lists/combo/simple-combo-dropdown-items/simple-combo-dropdown-items.component.html
@@ -0,0 +1,23 @@
+
+
+ @for (size of sizes; track size.label) {
+
{{ size.label }}
+ }
+ @for (row of rows; track row.label) {
+
{{ row.label }}
+ @for (size of sizes; track size.label) {
+
+
+
+
+
+ }
+ }
+
diff --git a/src/app/lists/combo/simple-combo-dropdown-items/simple-combo-dropdown-items.component.scss b/src/app/lists/combo/simple-combo-dropdown-items/simple-combo-dropdown-items.component.scss
new file mode 100644
index 0000000000..b0d4e07445
--- /dev/null
+++ b/src/app/lists/combo/simple-combo-dropdown-items/simple-combo-dropdown-items.component.scss
@@ -0,0 +1,82 @@
+:host {
+ display: flex;
+ align-items: flex-start;
+ justify-content: center;
+ height: 100%;
+ padding: 2rem 1rem;
+}
+
+.items {
+ display: grid;
+ grid-template-columns: max-content repeat(3, minmax(0, 11.25rem));
+ width: 100%;
+ justify-content: center;
+ align-items: start;
+ column-gap: 1.875rem;
+ row-gap: 1rem;
+}
+
+/* The second row is further from the first one than the column labels are. */
+.items > :nth-child(n + 9) {
+ margin-top: 1.5rem;
+}
+
+.dropdown {
+ width: 100%;
+ min-width: 0;
+}
+
+/* Only the dropdown of the combo is shown, so the input field is hidden. */
+.dropdown ::ng-deep igx-simple-combo > igx-input-group {
+ display: none;
+}
+
+/* The header row shows only the group header. */
+.header-only ::ng-deep igx-combo-item[role="option"] {
+ display: none;
+}
+
+/* The scroll track keeps the height of the hidden option too, so the header row leaves it out of
+ the layout and the list takes the height of the header in every theme and size. */
+.header-only ::ng-deep .igx-virtual-scroll__track {
+ display: contents;
+}
+
+.header-only ::ng-deep .igx-virtual-scroll__content {
+ position: static;
+}
+
+/* The list floats, so the element takes the height of its one item in each size. */
+.small {
+ --ig-size: var(--ig-size-small);
+
+ height: 1.75rem;
+}
+
+.medium {
+ --ig-size: var(--ig-size-medium);
+
+ height: 2rem;
+}
+
+.large {
+ --ig-size: var(--ig-size-large);
+
+ height: 2.5rem;
+}
+
+.state-label {
+ font: 400 13px / 20px "Aktiv Grotesk", sans-serif;
+ letter-spacing: 0.3px;
+ color: var(--ig-gray-600);
+}
+
+/* The row labels are centered on the small items. */
+.state-label:not(.state-column) {
+ justify-self: end;
+ line-height: 1.75rem;
+}
+
+.state-column {
+ justify-self: center;
+}
diff --git a/src/app/lists/combo/simple-combo-dropdown-items/simple-combo-dropdown-items.component.ts b/src/app/lists/combo/simple-combo-dropdown-items/simple-combo-dropdown-items.component.ts
new file mode 100644
index 0000000000..1aa24853a6
--- /dev/null
+++ b/src/app/lists/combo/simple-combo-dropdown-items/simple-combo-dropdown-items.component.ts
@@ -0,0 +1,70 @@
+import { Component, ElementRef, afterNextRender, inject, viewChildren } from '@angular/core';
+import { ConnectedPositioningStrategy, IBaseCancelableBrowserEventArgs, VerticalAlignment } from 'igniteui-angular/core';
+import { IgxSimpleComboComponent } from 'igniteui-angular/simple-combo';
+import { forkJoin, take } from 'rxjs';
+
+interface Option {
+ id: number;
+ name: string;
+ group?: string;
+}
+
+interface ItemRow {
+ label: string;
+ className: string;
+ groupKey?: string;
+ data: Option[];
+}
+
+interface DropdownSize {
+ label: string;
+ className: string;
+}
+
+@Component({
+ selector: 'app-simple-combo-dropdown-items',
+ styleUrls: ['./simple-combo-dropdown-items.component.scss'],
+ templateUrl: './simple-combo-dropdown-items.component.html',
+ imports: [IgxSimpleComboComponent]
+})
+export class SimpleComboDropdownItemsComponent {
+ // The dropdown opens at the top of the element around the combo, where the hidden input field is.
+ public positionStrategy = new ConnectedPositioningStrategy({
+ verticalStartPoint: VerticalAlignment.Top,
+ verticalDirection: VerticalAlignment.Bottom
+ });
+
+ public sizes: DropdownSize[] = [
+ { label: 'Small', className: 'small' },
+ { label: 'Medium', className: 'medium' },
+ { label: 'Large', className: 'large' }
+ ];
+
+ // The header row hides its option, so only the group header shows.
+ public rows: ItemRow[] = [
+ { label: 'Header', className: 'header-only', groupKey: 'group', data: [{ id: 1, name: 'Item', group: 'Header' }] },
+ { label: 'Item', className: 'item-only', data: [{ id: 1, name: 'Item' }] }
+ ];
+
+ private combos = viewChildren(IgxSimpleComboComponent);
+
+ constructor() {
+ const element = inject>(ElementRef).nativeElement;
+
+ // The combos are shown open. Opening a combo moves the focus to its list, so
+ // the sample is inert until the combos open and the page keeps its focus.
+ afterNextRender(() => {
+ const combos = this.combos();
+ element.inert = true;
+ forkJoin(combos.map((combo) => combo.opened.pipe(take(1)))).subscribe(() => {
+ element.inert = false;
+ });
+ combos.forEach((combo) => combo.open());
+ });
+ }
+
+ // The input fields are hidden, so the dropdowns stay open and each one works on its own.
+ public keepOpen(event: IBaseCancelableBrowserEventArgs): void {
+ event.cancel = true;
+ }
+}
diff --git a/src/app/lists/combo/simple-combo-dropdown-size/simple-combo-dropdown-size.component.html b/src/app/lists/combo/simple-combo-dropdown-size/simple-combo-dropdown-size.component.html
new file mode 100644
index 0000000000..56dccb41c8
--- /dev/null
+++ b/src/app/lists/combo/simple-combo-dropdown-size/simple-combo-dropdown-size.component.html
@@ -0,0 +1,19 @@
+
+ @for (size of sizes; track size.label) {
+
+
{{ size.label }}
+
+
+
+
+
+
+ }
+
diff --git a/src/app/lists/combo/simple-combo-dropdown-size/simple-combo-dropdown-size.component.scss b/src/app/lists/combo/simple-combo-dropdown-size/simple-combo-dropdown-size.component.scss
new file mode 100644
index 0000000000..57f75a5bce
--- /dev/null
+++ b/src/app/lists/combo/simple-combo-dropdown-size/simple-combo-dropdown-size.component.scss
@@ -0,0 +1,48 @@
+:host {
+ display: flex;
+ align-items: flex-start;
+ justify-content: center;
+ height: 100%;
+ padding: 2rem 1rem;
+}
+
+.sizes {
+ display: grid;
+ grid-template-columns: repeat(3, 11.25rem);
+ column-gap: 1rem;
+}
+
+.size {
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ gap: 1rem;
+}
+
+.dropdown {
+ width: 100%;
+}
+
+/* Only the dropdown of the combo is shown, so the input field is hidden. */
+.dropdown ::ng-deep igx-simple-combo > igx-input-group {
+ display: none;
+}
+
+
+.small {
+ --ig-size: var(--ig-size-small);
+}
+
+.medium {
+ --ig-size: var(--ig-size-medium);
+}
+
+.large {
+ --ig-size: var(--ig-size-large);
+}
+
+.state-label {
+ font: 400 13px / 20px "Aktiv Grotesk", sans-serif;
+ letter-spacing: 0.3px;
+ color: var(--ig-gray-600);
+}
diff --git a/src/app/lists/combo/simple-combo-dropdown-size/simple-combo-dropdown-size.component.ts b/src/app/lists/combo/simple-combo-dropdown-size/simple-combo-dropdown-size.component.ts
new file mode 100644
index 0000000000..dd47db9451
--- /dev/null
+++ b/src/app/lists/combo/simple-combo-dropdown-size/simple-combo-dropdown-size.component.ts
@@ -0,0 +1,53 @@
+import { Component, ElementRef, afterNextRender, inject, viewChildren } from '@angular/core';
+import { IgxSimpleComboComponent } from 'igniteui-angular/simple-combo';
+import { IBaseCancelableBrowserEventArgs } from 'igniteui-angular/core';
+import { forkJoin, take } from 'rxjs';
+
+interface Option {
+ id: number;
+ name: string;
+ group: string;
+}
+
+interface DropdownSize {
+ label: string;
+ className: string;
+}
+
+@Component({
+ selector: 'app-simple-combo-dropdown-size',
+ styleUrls: ['./simple-combo-dropdown-size.component.scss'],
+ templateUrl: './simple-combo-dropdown-size.component.html',
+ imports: [IgxSimpleComboComponent]
+})
+export class SimpleComboDropdownSizeComponent {
+ public options: Option[] = [1, 2, 3].map((id) => ({ id, name: 'Item', group: 'Header' }));
+
+ public sizes: DropdownSize[] = [
+ { label: 'Small', className: 'small' },
+ { label: 'Medium', className: 'medium' },
+ { label: 'Large', className: 'large' }
+ ];
+
+ private combos = viewChildren(IgxSimpleComboComponent);
+
+ constructor() {
+ const element = inject>(ElementRef).nativeElement;
+
+ // The combos are shown open. Opening a combo moves the focus to its list, so
+ // the sample is inert until the combos open and the page keeps its focus.
+ afterNextRender(() => {
+ const combos = this.combos();
+ element.inert = true;
+ forkJoin(combos.map((combo) => combo.opened.pipe(take(1)))).subscribe(() => {
+ element.inert = false;
+ });
+ combos.forEach((combo) => combo.open());
+ });
+ }
+
+ // The input fields are hidden, so the dropdowns stay open and each one works on its own.
+ public keepOpen(event: IBaseCancelableBrowserEventArgs): void {
+ event.cancel = true;
+ }
+}
diff --git a/src/app/lists/combo/simple-combo-main/simple-combo-main.component.html b/src/app/lists/combo/simple-combo-main/simple-combo-main.component.html
deleted file mode 100644
index 5aabfc2d6a..0000000000
--- a/src/app/lists/combo/simple-combo-main/simple-combo-main.component.html
+++ /dev/null
@@ -1,27 +0,0 @@
-
-
-
-
- @for (range of ranges; track range) {
-
- }
-
-
-
-
diff --git a/src/app/lists/combo/simple-combo-main/simple-combo-main.component.scss b/src/app/lists/combo/simple-combo-main/simple-combo-main.component.scss
deleted file mode 100644
index 2c02a3581b..0000000000
--- a/src/app/lists/combo/simple-combo-main/simple-combo-main.component.scss
+++ /dev/null
@@ -1,15 +0,0 @@
-.sample-wrapper {
- padding: 1rem 1rem 6rem 1rem;
- width: 100%;
- height: 100%;
-}
-.combo-section {
- display: flex;
- justify-content: space-between;
- margin-bottom: 3rem;
-}
-
-.input-container {
- flex-grow: 1;
- max-width: 300px;
-}
diff --git a/src/app/lists/combo/simple-combo-main/simple-combo-main.component.ts b/src/app/lists/combo/simple-combo-main/simple-combo-main.component.ts
deleted file mode 100644
index a5c86e96de..0000000000
--- a/src/app/lists/combo/simple-combo-main/simple-combo-main.component.ts
+++ /dev/null
@@ -1,80 +0,0 @@
-import { Component, OnInit } from '@angular/core';
-import { IgRect } from 'igniteui-angular-core';
-import { StockData, updatedStockData } from '../../../data/stocks-data';
-import { IgxSimpleComboComponent } from 'igniteui-angular/simple-combo';
-import { IgxButtonGroupComponent } from 'igniteui-angular/button-group';
-import { IgxButtonDirective } from 'igniteui-angular/directives';
-import { FormsModule } from '@angular/forms';
-
-import { IgxFinancialChartCoreModule, IgxFinancialChartModule } from 'igniteui-angular-charts';
-
-@Component({
- selector: 'app-simple-combo-main',
- templateUrl: 'simple-combo-main.component.html',
- styleUrls: ['simple-combo-main.component.scss'],
- imports: [IgxSimpleComboComponent, FormsModule, IgxButtonGroupComponent, IgxButtonDirective, IgxFinancialChartModule, IgxFinancialChartCoreModule]
-})
-export class SimpleComboMainComponent implements OnInit {
- public data: StockData[] = updatedStockData;
- public selectedRange: string;
- public trendLineType: string;
- public trendLineTypes: string[] = [
- 'CubicFit',
- 'LinearFit',
- 'QuinticFit',
- 'QuarticFit',
- 'ExponentialFit',
- 'PowerLawFit',
- 'LogarithmicFit',
- 'CumulativeAverage',
- 'ExponentialAverage',
- 'SimpleAverage',
- 'ModifiedAverage',
- 'WeightedAverage',
- 'None'
- ];
- public windowRect: IgRect = { top: 0, left: 0, width: 1, height: 1 };
- public ranges: string[] = ['1M', '3M', '6M', 'YTD', '1Y', 'ALL'];
-
- public rangeButtonClick(range: string) {
- this.selectedRange = range;
- let startDate = new Date(Date.now());
- switch (range) {
- case '1M':
- startDate.setMonth(startDate.getMonth() - 1);
- break;
- case '3M':
- startDate.setMonth(startDate.getMonth() - 3);
- break;
- case '6M':
- startDate.setMonth(startDate.getMonth() - 6);
- break;
- case 'YTD':
- startDate = new Date(startDate.getFullYear(), 0, 1);
- break;
- case '1Y':
- startDate.setFullYear(startDate.getFullYear() - 1);
- break;
- case 'ALL':
- startDate = this.data[0].time;
- break;
- }
- this.windowRect = this.getWindowRect(startDate);
- }
-
- public ngOnInit(): void {
- this.selectedRange = this.ranges[0];
- this.trendLineType = this.trendLineTypes[0];
- this.rangeButtonClick(this.selectedRange);
- }
-
- private getWindowRect(startDate: Date): IgRect {
- const startDateIndex = this.data.findIndex(d => d.time > startDate);
- return {
- top: 0,
- left: startDateIndex / this.data.length,
- width: 1 - startDateIndex / this.data.length,
- height: 1
- };
- }
-}
diff --git a/src/app/lists/combo/simple-combo-overview/simple-combo-overview.component.html b/src/app/lists/combo/simple-combo-overview/simple-combo-overview.component.html
new file mode 100644
index 0000000000..56412b28bd
--- /dev/null
+++ b/src/app/lists/combo/simple-combo-overview/simple-combo-overview.component.html
@@ -0,0 +1,11 @@
+
+
+
diff --git a/src/app/lists/combo/simple-combo-overview/simple-combo-overview.component.scss b/src/app/lists/combo/simple-combo-overview/simple-combo-overview.component.scss
new file mode 100644
index 0000000000..3fe352fe48
--- /dev/null
+++ b/src/app/lists/combo/simple-combo-overview/simple-combo-overview.component.scss
@@ -0,0 +1,15 @@
+:host {
+ /* styles.scss asks the page for the large size; the combo keeps its own
+ default medium size here. */
+ --ig-size: var(--ig-size-medium);
+
+ display: flex;
+ align-items: flex-start;
+ justify-content: center;
+ height: 100%;
+ padding: 2rem 1rem;
+}
+
+.country {
+ width: 22.5rem;
+}
diff --git a/src/app/lists/combo/simple-combo-overview/simple-combo-overview.component.ts b/src/app/lists/combo/simple-combo-overview/simple-combo-overview.component.ts
new file mode 100644
index 0000000000..452a8b7537
--- /dev/null
+++ b/src/app/lists/combo/simple-combo-overview/simple-combo-overview.component.ts
@@ -0,0 +1,44 @@
+import { Component, ElementRef, afterNextRender, inject, viewChild } from '@angular/core';
+import { IgxSimpleComboComponent } from 'igniteui-angular/simple-combo';
+import { IgxLabelDirective } from 'igniteui-angular/input-group';
+import { take } from 'rxjs';
+
+interface Country {
+ id: string;
+ name: string;
+ region: string;
+}
+
+@Component({
+ selector: 'app-simple-combo-overview',
+ styleUrls: ['./simple-combo-overview.component.scss'],
+ templateUrl: './simple-combo-overview.component.html',
+ imports: [IgxSimpleComboComponent, IgxLabelDirective]
+})
+export class SimpleComboOverviewComponent {
+ public countries: Country[] = [
+ { id: 'BY', name: 'Belarus', region: 'Europe' },
+ { id: 'BE', name: 'Belgium', region: 'Europe' },
+ { id: 'BG', name: 'Bulgaria', region: 'Europe' },
+ { id: 'HR', name: 'Croatia', region: 'Europe' },
+ { id: 'CZ', name: 'Czechia', region: 'Europe' },
+ { id: 'DK', name: 'Denmark', region: 'Europe' }
+ ];
+
+ private combo = viewChild.required(IgxSimpleComboComponent);
+
+ constructor() {
+ const element = inject>(ElementRef).nativeElement;
+
+ // The combo is shown open. Opening it moves the focus to its list, so the
+ // sample is inert until the combo opens and the page keeps its focus.
+ afterNextRender(() => {
+ const combo = this.combo();
+ element.inert = true;
+ combo.opened.pipe(take(1)).subscribe(() => {
+ element.inert = false;
+ });
+ combo.open();
+ });
+ }
+}
diff --git a/src/app/lists/combo/simple-combo-size/simple-combo-size.component.html b/src/app/lists/combo/simple-combo-size/simple-combo-size.component.html
new file mode 100644
index 0000000000..23d29512ad
--- /dev/null
+++ b/src/app/lists/combo/simple-combo-size/simple-combo-size.component.html
@@ -0,0 +1,21 @@
+
+
+ @for (size of sizes; track size.label) {
+ {{ size.label }}
+ }
+ @for (type of types; track type.label) {
+ {{ type.label }}
+ @for (size of sizes; track size.label) {
+
+
+ }
+ }
+
diff --git a/src/app/lists/combo/simple-combo-size/simple-combo-size.component.scss b/src/app/lists/combo/simple-combo-size/simple-combo-size.component.scss
new file mode 100644
index 0000000000..a89cfd43a3
--- /dev/null
+++ b/src/app/lists/combo/simple-combo-size/simple-combo-size.component.scss
@@ -0,0 +1,44 @@
+:host {
+ display: flex;
+ align-items: flex-start;
+ justify-content: center;
+ height: 100%;
+ padding: 2rem 1rem;
+}
+
+.sizes {
+ display: grid;
+ grid-template-columns: max-content repeat(3, minmax(0, 17.4375rem));
+ width: 100%;
+ justify-content: center;
+ grid-template-rows: auto repeat(2, 17rem);
+ align-items: start;
+ column-gap: 1.75rem;
+ row-gap: 1rem;
+}
+
+.type-label {
+ padding-top: 0.75rem;
+}
+
+.small {
+ --ig-size: var(--ig-size-small);
+}
+
+.medium {
+ --ig-size: var(--ig-size-medium);
+}
+
+.large {
+ --ig-size: var(--ig-size-large);
+}
+
+.state-label {
+ font: 400 13px / 20px "Aktiv Grotesk", sans-serif;
+ letter-spacing: 0.3px;
+ color: var(--ig-gray-600);
+}
+
+.state-column {
+ justify-self: center;
+}
diff --git a/src/app/lists/combo/simple-combo-size/simple-combo-size.component.ts b/src/app/lists/combo/simple-combo-size/simple-combo-size.component.ts
new file mode 100644
index 0000000000..0f1324b698
--- /dev/null
+++ b/src/app/lists/combo/simple-combo-size/simple-combo-size.component.ts
@@ -0,0 +1,62 @@
+import { Component, ElementRef, afterNextRender, inject, viewChildren } from '@angular/core';
+import { IgxSimpleComboComponent } from 'igniteui-angular/simple-combo';
+import { IgxInputGroupType } from 'igniteui-angular/input-group';
+import { forkJoin, take } from 'rxjs';
+
+interface Country {
+ id: string;
+ name: string;
+ region: string;
+}
+
+interface ComboSize {
+ label: string;
+ className: string;
+}
+
+interface ComboType {
+ label: string;
+ type: IgxInputGroupType;
+}
+
+@Component({
+ selector: 'app-simple-combo-size',
+ styleUrls: ['./simple-combo-size.component.scss'],
+ templateUrl: './simple-combo-size.component.html',
+ imports: [IgxSimpleComboComponent]
+})
+export class SimpleComboSizeComponent {
+ public countries: Country[] = [
+ { id: 'BY', name: 'Belarus', region: 'Europe' },
+ { id: 'BE', name: 'Belgium', region: 'Europe' },
+ { id: 'BG', name: 'Bulgaria', region: 'Europe' }
+ ];
+
+ public sizes: ComboSize[] = [
+ { label: 'Small', className: 'small' },
+ { label: 'Medium', className: 'medium' },
+ { label: 'Large', className: 'large' }
+ ];
+
+ public types: ComboType[] = [
+ { label: 'Border Type', type: 'border' },
+ { label: 'Box Type', type: 'box' }
+ ];
+
+ private combos = viewChildren(IgxSimpleComboComponent);
+
+ constructor() {
+ const element = inject>(ElementRef).nativeElement;
+
+ // The combos are shown open. Opening a combo moves the focus to its list, so
+ // the sample is inert until the combos open and the page keeps its focus.
+ afterNextRender(() => {
+ const combos = this.combos();
+ element.inert = true;
+ forkJoin(combos.map((combo) => combo.opened.pipe(take(1)))).subscribe(() => {
+ element.inert = false;
+ });
+ combos.forEach((combo) => combo.open());
+ });
+ }
+}
diff --git a/src/app/lists/combo/simple-combo-states/simple-combo-states.component.html b/src/app/lists/combo/simple-combo-states/simple-combo-states.component.html
new file mode 100644
index 0000000000..b4e11c1240
--- /dev/null
+++ b/src/app/lists/combo/simple-combo-states/simple-combo-states.component.html
@@ -0,0 +1,18 @@
+
+ @for (state of states; track state.label) {
+
+ {{ state.label }}
+
+
+
+ }
+
diff --git a/src/app/lists/combo/simple-combo-states/simple-combo-states.component.scss b/src/app/lists/combo/simple-combo-states/simple-combo-states.component.scss
new file mode 100644
index 0000000000..1e7cbd02a2
--- /dev/null
+++ b/src/app/lists/combo/simple-combo-states/simple-combo-states.component.scss
@@ -0,0 +1,69 @@
+:host {
+ /* styles.scss asks the page for the large size; the combos keep their own
+ default medium size here. */
+ --ig-size: var(--ig-size-medium);
+
+ display: flex;
+ align-items: flex-start;
+ justify-content: center;
+ height: 100%;
+ padding: 2rem 1rem;
+}
+
+.states {
+ display: grid;
+ grid-template-columns: repeat(3, minmax(0, 17.4375rem));
+ width: 100%;
+ justify-content: center;
+ column-gap: 1.75rem;
+ row-gap: 1.25rem;
+}
+
+.state {
+ display: flex;
+ flex-direction: column;
+ min-width: 0;
+ align-items: center;
+ gap: 0.5rem;
+}
+
+.state-combo {
+ width: 100%;
+ pointer-events: none;
+}
+
+/* The open dropdown takes no pointer input either. */
+.state-combo ::ng-deep .igx-overlay__content {
+ pointer-events: none;
+}
+
+/* The states belong to the combo's own input group, not to the search input in its dropdown. */
+.state-combo.hover > ::ng-deep igx-input-group {
+ --border-color: var(--hover-border-color);
+ --idle-bottom-line-color: var(--hover-bottom-line-color);
+}
+
+.state-combo.focused > ::ng-deep igx-input-group {
+ --border-color: var(--focused-border-color);
+ --idle-bottom-line-color: var(--focused-bottom-line-color);
+}
+
+.state-combo.focused > ::ng-deep igx-input-group .igx-input-group__bundle {
+ box-shadow: inset 0 0 0 0.125rem var(--focused-border-color);
+}
+
+.state-combo.focused > ::ng-deep igx-input-group .igx-input-group__toggle-button {
+ color: var(--toggle-button-foreground-focus);
+ background: var(--toggle-button-background-focus--border);
+}
+
+.state-combo.focused > ::ng-deep igx-input-group .igx-input-group__clear-button {
+ color: var(--clear-button-foreground-focus);
+ background: var(--clear-button-background-focus);
+}
+
+.state-label {
+ font: 400 13px / 20px "Aktiv Grotesk", sans-serif;
+ letter-spacing: 0.3px;
+ color: var(--ig-gray-600);
+}
diff --git a/src/app/lists/combo/simple-combo-states/simple-combo-states.component.ts b/src/app/lists/combo/simple-combo-states/simple-combo-states.component.ts
new file mode 100644
index 0000000000..cfd514d2bc
--- /dev/null
+++ b/src/app/lists/combo/simple-combo-states/simple-combo-states.component.ts
@@ -0,0 +1,67 @@
+import { Component, ElementRef, afterNextRender, inject, viewChildren } from '@angular/core';
+import { FormsModule } from '@angular/forms';
+import { IgxSimpleComboComponent } from 'igniteui-angular/simple-combo';
+import { OverlaySettings } from 'igniteui-angular/core';
+import { take } from 'rxjs';
+
+interface Country {
+ id: string;
+ name: string;
+ region: string;
+}
+
+interface ComboState {
+ label: string;
+ className?: string;
+ value?: string;
+ open?: boolean;
+}
+
+@Component({
+ selector: 'app-simple-combo-states',
+ styleUrls: ['./simple-combo-states.component.scss'],
+ templateUrl: './simple-combo-states.component.html',
+ imports: [IgxSimpleComboComponent, FormsModule]
+})
+export class SimpleComboStatesComponent {
+ public countries: Country[] = [
+ { id: 'BY', name: 'Belarus', region: 'Europe' },
+ { id: 'BE', name: 'Belgium', region: 'Europe' },
+ { id: 'BG', name: 'Bulgaria', region: 'Europe' }
+ ];
+
+ // Hover and focus come from the pointer and the keyboard, so the `hover` and `focused`
+ // classes show them on the display-only combos.
+ public states: ComboState[] = [
+ { label: 'Idle' },
+ { label: 'Idle & Hover', className: 'hover' },
+ { label: 'Filled', value: 'BY' },
+ { label: 'Filled & Hover', className: 'hover', value: 'BY' },
+ { label: 'Focused', className: 'focused', value: 'BY' },
+ { label: 'Focused (Dropdown On)', className: 'focused', value: 'BY', open: true }
+ ];
+
+ // The open dropdown is display-only, so a click elsewhere does not close it.
+ public overlaySettings: OverlaySettings = { closeOnOutsideClick: false };
+
+ private combos = viewChildren(IgxSimpleComboComponent);
+
+ constructor() {
+ // The combos are display-only, so the sample is inert. Opening a combo moves the focus
+ // to its list, and the inert sample also keeps the focus on the page.
+ inject>(ElementRef).nativeElement.inert = true;
+
+ // The last combo is shown with its dropdown open. The bound value filters its list by the
+ // text of the selected item, so the filter is cleared once the dropdown opens.
+ afterNextRender(() => {
+ this.combos()
+ .filter((_, index) => this.states[index].open)
+ .forEach((combo) => {
+ combo.opened.pipe(take(1)).subscribe(() => {
+ combo.filterValue = '';
+ });
+ combo.open();
+ });
+ });
+ }
+}
diff --git a/src/app/lists/combo/simple-combo-styling/layout.scss b/src/app/lists/combo/simple-combo-styling/layout.scss
deleted file mode 100644
index f01b3ab9dc..0000000000
--- a/src/app/lists/combo/simple-combo-styling/layout.scss
+++ /dev/null
@@ -1,4 +0,0 @@
-.sample-wrapper {
- width: 430px;
- margin: 16px;
-}
diff --git a/src/app/lists/combo/simple-combo-styling/simple-combo-styling.component.html b/src/app/lists/combo/simple-combo-styling/simple-combo-styling.component.html
index c07b0d5d04..a8375c778a 100644
--- a/src/app/lists/combo/simple-combo-styling/simple-combo-styling.component.html
+++ b/src/app/lists/combo/simple-combo-styling/simple-combo-styling.component.html
@@ -1,8 +1,10 @@
-
+
+
diff --git a/src/app/lists/combo/simple-combo-styling/simple-combo-styling.component.scss b/src/app/lists/combo/simple-combo-styling/simple-combo-styling.component.scss
index 597944a0b7..ea3135e7a0 100644
--- a/src/app/lists/combo/simple-combo-styling/simple-combo-styling.component.scss
+++ b/src/app/lists/combo/simple-combo-styling/simple-combo-styling.component.scss
@@ -1,29 +1,42 @@
-@use "layout.scss";
-@use "igniteui-angular/theming" as *;
+@use "sass:meta";
+@include meta.load-css("igniteui-angular/lib/core/styles/themes/presets/igniteui-bootstrap-light");
-$custom-simple-combo-theme: combo-theme(
- $empty-list-background: #1a5214
-);
+app-simple-combo-styling {
+ /* styles.scss asks the page for the large size; the combo keeps its own
+ default medium size here. */
+ --ig-size: var(--ig-size-medium);
-$custom-drop-down-theme: drop-down-theme(
- $background-color: #d9f5d6,
- $header-text-color: #1a5214,
- $item-text-color: #1a5214,
-
- $focused-item-background: #72da67,
- $focused-item-text-color: #1a5214,
- $hover-item-background: #a0e698,
- $hover-item-text-color: #1a5214,
+ display: flex;
+ align-items: flex-start;
+ justify-content: center;
+ height: 100%;
+ padding: 2rem 1rem;
+}
- $selected-item-background: #a0e698,
- $selected-item-text-color: #1a5214,
- $selected-hover-item-background: #72da67,
- $selected-hover-item-text-color: #1a5214,
- $selected-focus-item-background: #72da67,
- $selected-focus-item-text-color: #1a5214,
-);
+.styled-combo {
+ --ig-font-family: "Titillium Web", sans-serif;
+ --ig-input-group-border-color: #1377d5;
+ --ig-input-group-hover-border-color: #1377d5;
+ --ig-input-group-focused-border-color: #1377d5;
+ --ig-input-group-filled-text-color: #212121;
+ --ig-input-group-focused-bottom-line-color: #007ccf;
+ --ig-combo-toggle-button-background: #e0e0e0;
+ --ig-combo-toggle-button-background-focus: #e0e0e0;
+ --ig-combo-toggle-button-background-focus--border: #e0e0e0;
+ --ig-combo-toggle-button-foreground: #212121;
+ --ig-combo-toggle-button-foreground-focus: #212121;
+ --ig-combo-toggle-button-foreground-filled: #212121;
+ --ig-drop-down-background-color: #e6f2ff;
+ --ig-drop-down-header-text-color: #005dcc;
+ --ig-drop-down-item-text-color: #212121;
+ --ig-drop-down-hover-item-background: #cce4ff;
+ --ig-drop-down-focused-item-background: #cce4ff;
+ --ig-drop-down-selected-item-background: #4ca7e3;
+ --ig-drop-down-selected-item-text-color: #000;
+ --ig-drop-down-selected-hover-item-background: #4ca7e3;
+ --ig-drop-down-selected-hover-item-text-color: #000;
+ --ig-drop-down-selected-focus-item-background: #4ca7e3;
+ --ig-drop-down-selected-focus-item-text-color: #000;
-:host {
- @include tokens($custom-simple-combo-theme);
- @include tokens($custom-drop-down-theme);
+ width: 17.4375rem;
}
diff --git a/src/app/lists/combo/simple-combo-styling/simple-combo-styling.component.ts b/src/app/lists/combo/simple-combo-styling/simple-combo-styling.component.ts
index b28e790883..d1b66c0f10 100644
--- a/src/app/lists/combo/simple-combo-styling/simple-combo-styling.component.ts
+++ b/src/app/lists/combo/simple-combo-styling/simple-combo-styling.component.ts
@@ -1,19 +1,46 @@
-import { Component, ElementRef, OnInit, inject } from '@angular/core';
-import { City, getCitiesByPopulation } from '../../../data/cities15000-regions-countries';
+import { Component, ElementRef, ViewEncapsulation, afterNextRender, inject, viewChild } from '@angular/core';
+import { FormsModule } from '@angular/forms';
import { IgxSimpleComboComponent } from 'igniteui-angular/simple-combo';
+import { take } from 'rxjs';
+
+interface Country {
+ id: string;
+ name: string;
+ region: string;
+}
@Component({
selector: 'app-simple-combo-styling',
- templateUrl: 'simple-combo-styling.component.html',
- styleUrls: ['simple-combo-styling.component.scss'],
- imports: [IgxSimpleComboComponent]
+ encapsulation: ViewEncapsulation.None,
+ styleUrls: ['./simple-combo-styling.component.scss'],
+ templateUrl: './simple-combo-styling.component.html',
+ imports: [IgxSimpleComboComponent, FormsModule]
})
-export class SimpleComboStylingComponent implements OnInit {
- element = inject(ElementRef);
+export class SimpleComboStylingComponent {
+ public countries: Country[] = [
+ { id: 'BY', name: 'Belarus', region: 'Europe' },
+ { id: 'BE', name: 'Belgium', region: 'Europe' },
+ { id: 'BG', name: 'Bulgaria', region: 'Europe' }
+ ];
+
+ public selection = 'BY';
+
+ private combo = viewChild.required(IgxSimpleComboComponent);
- public cities: City[];
+ constructor() {
+ const element = inject>(ElementRef).nativeElement;
- ngOnInit(): void {
- this.cities = getCitiesByPopulation(10000000);
+ // The combo is shown open. Opening it moves the focus to its list, so the sample is inert
+ // until the combo opens and the page keeps its focus. The bound value filters the list by
+ // the text of the selected item, so the filter is cleared once the dropdown opens.
+ afterNextRender(() => {
+ const combo = this.combo();
+ element.inert = true;
+ combo.opened.pipe(take(1)).subscribe(() => {
+ element.inert = false;
+ combo.filterValue = '';
+ });
+ combo.open();
+ });
}
}
diff --git a/src/app/lists/combo/simple-combo-tailwind-styling/simple-combo-tailwind-styling.component.html b/src/app/lists/combo/simple-combo-tailwind-styling/simple-combo-tailwind-styling.component.html
new file mode 100644
index 0000000000..245fdc03b8
--- /dev/null
+++ b/src/app/lists/combo/simple-combo-tailwind-styling/simple-combo-tailwind-styling.component.html
@@ -0,0 +1,10 @@
+
diff --git a/src/app/lists/combo/simple-combo-tailwind-styling/simple-combo-tailwind-styling.component.scss b/src/app/lists/combo/simple-combo-tailwind-styling/simple-combo-tailwind-styling.component.scss
new file mode 100644
index 0000000000..1043be4618
--- /dev/null
+++ b/src/app/lists/combo/simple-combo-tailwind-styling/simple-combo-tailwind-styling.component.scss
@@ -0,0 +1,24 @@
+@use "sass:meta";
+@import url("https://use.typekit.net/bng2zgw.css");
+@include meta.load-css("igniteui-angular/lib/core/styles/themes/presets/igniteui-bootstrap-light");
+
+app-simple-combo-tailwind-styling {
+ /* The combo palette that the utilities in the template read. */
+ --color-combo-border: #a9b8cc;
+ --color-combo-toggle: #e5ebf3;
+ --color-combo-surface: #f3f6fa;
+ --color-combo-selected: #d2dbe8;
+ --color-combo-accent: #7d91ab;
+ --color-combo-header: #005dcc;
+ --color-combo-ink: #212121;
+
+ /* styles.scss asks the page for the large size; the combo keeps its own
+ default medium size here. */
+ --ig-size: var(--ig-size-medium);
+
+ display: flex;
+ align-items: flex-start;
+ justify-content: center;
+ height: 100%;
+ padding: 2rem 1rem;
+}
diff --git a/src/app/lists/combo/simple-combo-tailwind-styling/simple-combo-tailwind-styling.component.ts b/src/app/lists/combo/simple-combo-tailwind-styling/simple-combo-tailwind-styling.component.ts
new file mode 100644
index 0000000000..4372e44921
--- /dev/null
+++ b/src/app/lists/combo/simple-combo-tailwind-styling/simple-combo-tailwind-styling.component.ts
@@ -0,0 +1,46 @@
+import { Component, ElementRef, ViewEncapsulation, afterNextRender, inject, viewChild } from '@angular/core';
+import { FormsModule } from '@angular/forms';
+import { IgxSimpleComboComponent } from 'igniteui-angular/simple-combo';
+import { take } from 'rxjs';
+
+interface Country {
+ id: string;
+ name: string;
+ region: string;
+}
+
+@Component({
+ selector: 'app-simple-combo-tailwind-styling',
+ encapsulation: ViewEncapsulation.None,
+ styleUrls: ['./simple-combo-tailwind-styling.component.scss'],
+ templateUrl: './simple-combo-tailwind-styling.component.html',
+ imports: [IgxSimpleComboComponent, FormsModule]
+})
+export class SimpleComboTailwindStylingComponent {
+ public countries: Country[] = [
+ { id: 'BY', name: 'Belarus', region: 'Europe' },
+ { id: 'BE', name: 'Belgium', region: 'Europe' },
+ { id: 'BG', name: 'Bulgaria', region: 'Europe' }
+ ];
+
+ public selection = 'BY';
+
+ private combo = viewChild.required(IgxSimpleComboComponent);
+
+ constructor() {
+ const element = inject>(ElementRef).nativeElement;
+
+ // The combo is shown open. Opening it moves the focus to its list, so the sample is inert
+ // until the combo opens and the page keeps its focus. The bound value filters the list by
+ // the text of the selected item, so the filter is cleared once the dropdown opens.
+ afterNextRender(() => {
+ const combo = this.combo();
+ element.inert = true;
+ combo.opened.pipe(take(1)).subscribe(() => {
+ element.inert = false;
+ combo.filterValue = '';
+ });
+ combo.open();
+ });
+ }
+}
diff --git a/src/app/lists/combo/simple-combo-type/simple-combo-type.component.html b/src/app/lists/combo/simple-combo-type/simple-combo-type.component.html
new file mode 100644
index 0000000000..1d1dd98c7d
--- /dev/null
+++ b/src/app/lists/combo/simple-combo-type/simple-combo-type.component.html
@@ -0,0 +1,20 @@
+
+ @for (type of types; track type.label) {
+
+ {{ type.label }}
+
+
+
+
+
+
+
+ }
+
diff --git a/src/app/lists/combo/simple-combo-type/simple-combo-type.component.scss b/src/app/lists/combo/simple-combo-type/simple-combo-type.component.scss
new file mode 100644
index 0000000000..f45cb9a3d9
--- /dev/null
+++ b/src/app/lists/combo/simple-combo-type/simple-combo-type.component.scss
@@ -0,0 +1,34 @@
+:host {
+ /* styles.scss asks the page for the large size; the combos keep their own
+ default medium size here. */
+ --ig-size: var(--ig-size-medium);
+
+ display: flex;
+ align-items: flex-start;
+ justify-content: center;
+ height: 100%;
+ padding: 2rem 1rem;
+}
+
+.types {
+ display: grid;
+ grid-template-columns: repeat(2, minmax(0, 22.5rem));
+ width: 100%;
+ justify-content: center;
+ column-gap: 3.75rem;
+}
+
+.type {
+ display: flex;
+ flex-direction: column;
+ min-width: 0;
+ gap: 0.75rem;
+}
+
+.type-label {
+ justify-self: center;
+ text-align: center;
+ font: 400 13px / 20px "Aktiv Grotesk", sans-serif;
+ letter-spacing: 0.3px;
+ color: var(--ig-gray-600);
+}
diff --git a/src/app/lists/combo/simple-combo-type/simple-combo-type.component.ts b/src/app/lists/combo/simple-combo-type/simple-combo-type.component.ts
new file mode 100644
index 0000000000..18f7450a35
--- /dev/null
+++ b/src/app/lists/combo/simple-combo-type/simple-combo-type.component.ts
@@ -0,0 +1,51 @@
+import { Component, ElementRef, afterNextRender, inject, viewChildren } from '@angular/core';
+import { IgxSimpleComboComponent } from 'igniteui-angular/simple-combo';
+import { IgxIconComponent, IgxIconService } from 'igniteui-angular/icon';
+import { IgxInputGroupType, IgxLabelDirective, IgxPrefixDirective } from 'igniteui-angular/input-group';
+import { forkJoin, take } from 'rxjs';
+
+const languageIcon = '';
+
+interface Option {
+ id: number;
+ name: string;
+ group: string;
+}
+
+interface ComboType {
+ label: string;
+ type: IgxInputGroupType;
+}
+
+@Component({
+ selector: 'app-simple-combo-type',
+ styleUrls: ['./simple-combo-type.component.scss'],
+ templateUrl: './simple-combo-type.component.html',
+ imports: [IgxSimpleComboComponent, IgxLabelDirective, IgxPrefixDirective, IgxIconComponent]
+})
+export class SimpleComboTypeComponent {
+ public options: Option[] = [1, 2, 3, 4, 5].map((id) => ({ id, name: 'Item', group: 'Header' }));
+
+ public types: ComboType[] = [
+ { label: 'Border Type', type: 'border' },
+ { label: 'Box Type', type: 'box' }
+ ];
+
+ private combos = viewChildren(IgxSimpleComboComponent);
+
+ constructor() {
+ const element = inject>(ElementRef).nativeElement;
+ inject(IgxIconService).addSvgIconFromText('language', languageIcon, 'material');
+
+ // The combos are shown open. Opening a combo moves the focus to its list, so
+ // the sample is inert until the combos open and the page keeps its focus.
+ afterNextRender(() => {
+ const combos = this.combos();
+ element.inert = true;
+ forkJoin(combos.map((combo) => combo.opened.pipe(take(1)))).subscribe(() => {
+ element.inert = false;
+ });
+ combos.forEach((combo) => combo.open());
+ });
+ }
+}
diff --git a/src/app/lists/combo/simple-combo-usage/simple-combo-usage.component.html b/src/app/lists/combo/simple-combo-usage/simple-combo-usage.component.html
deleted file mode 100644
index 0e722240a3..0000000000
--- a/src/app/lists/combo/simple-combo-usage/simple-combo-usage.component.html
+++ /dev/null
@@ -1,12 +0,0 @@
-
-
-
-
-
- @if (!!selectedCity) {
-
-
Selected City's Id:
-
{{ selectedCity }}
-
- }
-
diff --git a/src/app/lists/combo/simple-combo-usage/simple-combo-usage.component.scss b/src/app/lists/combo/simple-combo-usage/simple-combo-usage.component.scss
deleted file mode 100644
index c9fbf094c5..0000000000
--- a/src/app/lists/combo/simple-combo-usage/simple-combo-usage.component.scss
+++ /dev/null
@@ -1,9 +0,0 @@
-.sample-wrapper {
- width: 430px;
- margin: 16px;
-}
-
-.combo-section {
- width: 100%;
- margin-bottom: 16px;
-}
diff --git a/src/app/lists/combo/simple-combo-usage/simple-combo-usage.component.ts b/src/app/lists/combo/simple-combo-usage/simple-combo-usage.component.ts
deleted file mode 100644
index 1a87c034c9..0000000000
--- a/src/app/lists/combo/simple-combo-usage/simple-combo-usage.component.ts
+++ /dev/null
@@ -1,21 +0,0 @@
-import { Component, OnInit } from '@angular/core';
-import { City, getCitiesByPopulation } from '../../../data/cities15000-regions-countries';
-import { IgxSimpleComboComponent } from 'igniteui-angular/simple-combo';
-import { FormsModule } from '@angular/forms';
-
-
-@Component({
- selector: 'app-simple-combo-usage',
- templateUrl: 'simple-combo-usage.component.html',
- styleUrls: ['simple-combo-usage.component.scss'],
- imports: [IgxSimpleComboComponent, FormsModule]
-})
-export class SimpleComboUsageComponent implements OnInit {
- public cities: City[];
- public selectedCity: number;
-
- public ngOnInit(): void {
- this.cities = getCitiesByPopulation(10000000);
- this.selectedCity = this.cities[0].id;
- }
-}
diff --git a/src/app/lists/combo/simple-combo-validation/simple-combo-validation.component.html b/src/app/lists/combo/simple-combo-validation/simple-combo-validation.component.html
new file mode 100644
index 0000000000..8f6f0da221
--- /dev/null
+++ b/src/app/lists/combo/simple-combo-validation/simple-combo-validation.component.html
@@ -0,0 +1,23 @@
+
+
+ @for (state of states; track state.label) {
+ {{ state.label }}
+ }
+ @for (type of types; track type.label) {
+ {{ type.label }}
+ @for (state of states; track state.label) {
+
+ @if (state.helperText) {
+ {{ state.helperText }}
+ }
+
+ }
+ }
+
diff --git a/src/app/lists/combo/simple-combo-validation/simple-combo-validation.component.scss b/src/app/lists/combo/simple-combo-validation/simple-combo-validation.component.scss
new file mode 100644
index 0000000000..f9f40dcc31
--- /dev/null
+++ b/src/app/lists/combo/simple-combo-validation/simple-combo-validation.component.scss
@@ -0,0 +1,56 @@
+:host {
+ /* styles.scss asks the page for the large size; the combos keep their own
+ default medium size here. */
+ --ig-size: var(--ig-size-medium);
+
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ height: 100%;
+ padding: 1rem;
+}
+
+.states {
+ display: grid;
+ grid-template-columns: max-content repeat(3, minmax(0, 17.4375rem));
+ width: 100%;
+ justify-content: center;
+ align-items: start;
+ column-gap: 1.75rem;
+ row-gap: 1rem;
+}
+
+.type-label {
+ padding-top: 0.75rem;
+}
+
+.state-combo {
+ pointer-events: none;
+}
+
+/* The Material theme leaves the bottom line of a valid box combo uncolored, so the
+ success combos color it with the success color. */
+@container style(--ig-theme: material) {
+ .state-combo.success > ::ng-deep igx-input-group .igx-input-group__bundle::before {
+ border-block-end-color: var(--success-secondary-color);
+ }
+}
+
+.state-combo.warn > ::ng-deep igx-input-group {
+ --border-color: var(--warning-secondary-color);
+ --idle-bottom-line-color: var(--warning-secondary-color);
+}
+
+.state-combo.warn igx-hint {
+ color: var(--warning-secondary-color);
+}
+
+.state-label {
+ font: 400 13px / 20px "Aktiv Grotesk", sans-serif;
+ letter-spacing: 0.3px;
+ color: var(--ig-gray-600);
+}
+
+.state-column {
+ justify-self: center;
+}
diff --git a/src/app/lists/combo/simple-combo-validation/simple-combo-validation.component.ts b/src/app/lists/combo/simple-combo-validation/simple-combo-validation.component.ts
new file mode 100644
index 0000000000..52e6add482
--- /dev/null
+++ b/src/app/lists/combo/simple-combo-validation/simple-combo-validation.component.ts
@@ -0,0 +1,70 @@
+import { ChangeDetectorRef, Component, ElementRef, afterNextRender, inject, viewChildren } from '@angular/core';
+import { IgxSimpleComboComponent } from 'igniteui-angular/simple-combo';
+import { IgxHintDirective, IgxInputGroupType, IgxInputState } from 'igniteui-angular/input-group';
+
+interface Country {
+ id: string;
+ name: string;
+ region: string;
+}
+
+interface ValidationState {
+ label: string;
+ className: string;
+ state?: IgxInputState;
+ helperText?: string;
+}
+
+interface ComboType {
+ label: string;
+ type: IgxInputGroupType;
+}
+
+@Component({
+ selector: 'app-simple-combo-validation',
+ styleUrls: ['./simple-combo-validation.component.scss'],
+ templateUrl: './simple-combo-validation.component.html',
+ imports: [IgxSimpleComboComponent, IgxHintDirective]
+})
+export class SimpleComboValidationComponent {
+ public countries: Country[] = [
+ { id: 'BY', name: 'Belarus', region: 'Europe' },
+ { id: 'BE', name: 'Belgium', region: 'Europe' },
+ { id: 'BG', name: 'Bulgaria', region: 'Europe' }
+ ];
+
+ // The combo has built-in valid and invalid states, so the `warn` class shows the
+ // warning state on the display-only combos.
+ public states: ValidationState[] = [
+ { label: 'Success', className: 'success', state: IgxInputState.VALID },
+ { label: 'Warn', className: 'warn', helperText: 'Additional fees might be included' },
+ { label: 'Error', className: 'error', state: IgxInputState.INVALID, helperText: 'Please select a different region' }
+ ];
+
+ public types: ComboType[] = [
+ { label: 'Border Type', type: 'border' },
+ { label: 'Box Type', type: 'box' }
+ ];
+
+ private combos = viewChildren(IgxSimpleComboComponent);
+ private cdr = inject(ChangeDetectorRef);
+
+ constructor() {
+ // The combos are display-only, so the sample is inert.
+ inject>(ElementRef).nativeElement.inert = true;
+
+ afterNextRender(() => {
+ for (const combo of this.combos()) {
+ combo.select('BY');
+ }
+ this.setStates();
+ });
+ }
+
+ private setStates() {
+ this.combos().forEach((combo, index) => {
+ combo.valid = this.states[index % this.states.length].state ?? IgxInputState.INITIAL;
+ });
+ this.cdr.markForCheck();
+ }
+}
diff --git a/src/app/lists/lists-routes-data.ts b/src/app/lists/lists-routes-data.ts
index e2d25c0a23..8bcd96109e 100644
--- a/src/app/lists/lists-routes-data.ts
+++ b/src/app/lists/lists-routes-data.ts
@@ -10,8 +10,24 @@ export const listsRoutesData = {
'combo-tailwind-styling': { displayName: 'Combo Tailwind Styling', parentName: 'Combo' },
'combo-template': { displayName: 'Templating Combo', parentName: 'Combo' },
'combo-overlay': { displayName: 'Combo Overlay', parentName: 'Combo' },
- 'simple-combo-main': { displayName: 'Main Simple Combo', parentName: 'Combo'},
- 'simple-combo-usage': { displayName: 'Usage Simple Combo', parentName: 'Combo'},
+ 'combo-overview': { displayName: 'Combo Overview', parentName: 'Combo' },
+ 'combo-type': { displayName: 'Combo Type', parentName: 'Combo' },
+ 'combo-disabled': { displayName: 'Combo Disabled', parentName: 'Combo' },
+ 'combo-size': { displayName: 'Combo Size', parentName: 'Combo' },
+ 'combo-states': { displayName: 'Combo States', parentName: 'Combo' },
+ 'combo-validation': { displayName: 'Combo Validation', parentName: 'Combo' },
+ 'combo-dropdown-size': { displayName: 'Combo Dropdown Size', parentName: 'Combo' },
+ 'combo-search-input': { displayName: 'Combo Search Input', parentName: 'Combo' },
+ 'simple-combo-overview': { displayName: 'Simple Combo Overview', parentName: 'Combo' },
+ 'simple-combo-type': { displayName: 'Simple Combo Type', parentName: 'Combo' },
+ 'simple-combo-disabled': { displayName: 'Simple Combo Disabled', parentName: 'Combo' },
+ 'simple-combo-size': { displayName: 'Simple Combo Size', parentName: 'Combo' },
+ 'simple-combo-states': { displayName: 'Simple Combo States', parentName: 'Combo' },
+ 'simple-combo-validation': { displayName: 'Simple Combo Validation', parentName: 'Combo' },
+ 'simple-combo-dropdown-size': { displayName: 'Simple Combo Dropdown Size', parentName: 'Combo' },
+ 'simple-combo-dropdown-items': { displayName: 'Simple Combo Dropdown Items', parentName: 'Combo' },
+ 'simple-combo-dropdown-items-states': { displayName: 'Simple Combo Dropdown Items States', parentName: 'Combo' },
+ 'simple-combo-tailwind-styling': { displayName: 'Simple Combo Tailwind Styling', parentName: 'Combo' },
'simple-combo-cascading': { displayName: 'Cascading Simple Combo', parentName: 'Combo'},
'simple-combo-styling': { displayName: 'Simple Combo Styling', parentName: 'Combo'},
'simple-combo-remote': { displayName: 'Remote Simple Combo', parentName: 'Combo'},
diff --git a/src/app/lists/lists.routes.ts b/src/app/lists/lists.routes.ts
index 3036dba6b7..c93da432aa 100644
--- a/src/app/lists/lists.routes.ts
+++ b/src/app/lists/lists.routes.ts
@@ -8,6 +8,14 @@ import { ComboStylingComponent } from './combo/combo-styling/combo-styling.compo
import { ComboTailwindStylingComponent } from './combo/combo-tailwind-styling/combo-tailwind-styling.component';
import { ComboValueKeyComponent } from './combo/combo-valuekey/combo-valuekey.component';
import { ComboTemplateComponent } from './combo/combo-template/combo-template.component';
+import { ComboOverviewComponent } from './combo/combo-overview/combo-overview.component';
+import { ComboTypeComponent } from './combo/combo-type/combo-type.component';
+import { ComboDisabledComponent } from './combo/combo-disabled/combo-disabled.component';
+import { ComboSizeComponent } from './combo/combo-size/combo-size.component';
+import { ComboStatesComponent } from './combo/combo-states/combo-states.component';
+import { ComboValidationComponent } from './combo/combo-validation/combo-validation.component';
+import { ComboDropdownSizeComponent } from './combo/combo-dropdown-size/combo-dropdown-size.component';
+import { ComboSearchInputComponent } from './combo/combo-search-input/combo-search-input.component';
import { ListChatSampleComponent } from './list/list-chat-sample/list-chat-sample.component';
import { ListSample1Component } from './list/list-sample-1/list-sample-1.component';
import { ListSample2Component } from './list/list-sample-2/list-sample-2.component';
@@ -24,8 +32,16 @@ import { TreeBasicSampleComponent } from './tree/tree-basic-sample/tree-basic-sa
import { TreeAdvancedSampleComponent } from './tree/tree-advanced-sample/tree-advanced-sample.component';
import { TreeStylingComponent } from './tree/tree-styling/tree-styling.component';
import { TreeTailwindStylingComponent } from './tree/tree-tailwind-styling/tree-tailwind-styling.component';
-import { SimpleComboMainComponent } from './combo/simple-combo-main/simple-combo-main.component';
-import { SimpleComboUsageComponent } from './combo/simple-combo-usage/simple-combo-usage.component';
+import { SimpleComboOverviewComponent } from './combo/simple-combo-overview/simple-combo-overview.component';
+import { SimpleComboTypeComponent } from './combo/simple-combo-type/simple-combo-type.component';
+import { SimpleComboDisabledComponent } from './combo/simple-combo-disabled/simple-combo-disabled.component';
+import { SimpleComboSizeComponent } from './combo/simple-combo-size/simple-combo-size.component';
+import { SimpleComboStatesComponent } from './combo/simple-combo-states/simple-combo-states.component';
+import { SimpleComboValidationComponent } from './combo/simple-combo-validation/simple-combo-validation.component';
+import { SimpleComboDropdownSizeComponent } from './combo/simple-combo-dropdown-size/simple-combo-dropdown-size.component';
+import { SimpleComboDropdownItemsComponent } from './combo/simple-combo-dropdown-items/simple-combo-dropdown-items.component';
+import { SimpleComboDropdownItemsStatesComponent } from './combo/simple-combo-dropdown-items-states/simple-combo-dropdown-items-states.component';
+import { SimpleComboTailwindStylingComponent } from './combo/simple-combo-tailwind-styling/simple-combo-tailwind-styling.component';
import { SimpleComboCascadingComponent } from './combo/simple-combo-cascading/simple-combo-cascading.component';
import { SimpleComboStylingComponent } from './combo/simple-combo-styling/simple-combo-styling.component';
import { ListItemSelectionComponent } from './list/list-item-selection/list-item-selection.component';
@@ -78,14 +94,94 @@ export const ListsRoutes: Routes = [
path: 'combo-overlay'
},
{
- component: SimpleComboMainComponent,
- data: listsRoutesData['simple-combo-main'],
- path: 'simple-combo-main'
+ component: ComboOverviewComponent,
+ data: listsRoutesData['combo-overview'],
+ path: 'combo-overview'
},
{
- component: SimpleComboUsageComponent,
- data: listsRoutesData['simple-combo-usage'],
- path: 'simple-combo-usage'
+ component: ComboTypeComponent,
+ data: listsRoutesData['combo-type'],
+ path: 'combo-type'
+ },
+ {
+ component: ComboDisabledComponent,
+ data: listsRoutesData['combo-disabled'],
+ path: 'combo-disabled'
+ },
+ {
+ component: ComboSizeComponent,
+ data: listsRoutesData['combo-size'],
+ path: 'combo-size'
+ },
+ {
+ component: ComboStatesComponent,
+ data: listsRoutesData['combo-states'],
+ path: 'combo-states'
+ },
+ {
+ component: ComboValidationComponent,
+ data: listsRoutesData['combo-validation'],
+ path: 'combo-validation'
+ },
+ {
+ component: ComboDropdownSizeComponent,
+ data: listsRoutesData['combo-dropdown-size'],
+ path: 'combo-dropdown-size'
+ },
+ {
+ component: ComboSearchInputComponent,
+ data: listsRoutesData['combo-search-input'],
+ path: 'combo-search-input'
+ },
+ {
+ component: SimpleComboOverviewComponent,
+ data: listsRoutesData['simple-combo-overview'],
+ path: 'simple-combo-overview'
+ },
+ {
+ component: SimpleComboTypeComponent,
+ data: listsRoutesData['simple-combo-type'],
+ path: 'simple-combo-type'
+ },
+ {
+ component: SimpleComboDisabledComponent,
+ data: listsRoutesData['simple-combo-disabled'],
+ path: 'simple-combo-disabled'
+ },
+ {
+ component: SimpleComboSizeComponent,
+ data: listsRoutesData['simple-combo-size'],
+ path: 'simple-combo-size'
+ },
+ {
+ component: SimpleComboStatesComponent,
+ data: listsRoutesData['simple-combo-states'],
+ path: 'simple-combo-states'
+ },
+ {
+ component: SimpleComboValidationComponent,
+ data: listsRoutesData['simple-combo-validation'],
+ path: 'simple-combo-validation'
+ },
+ {
+ component: SimpleComboDropdownSizeComponent,
+ data: listsRoutesData['simple-combo-dropdown-size'],
+ path: 'simple-combo-dropdown-size'
+ },
+ {
+ component: SimpleComboDropdownItemsComponent,
+ data: listsRoutesData['simple-combo-dropdown-items'],
+ path: 'simple-combo-dropdown-items'
+ },
+ {
+ component: SimpleComboDropdownItemsStatesComponent,
+ data: listsRoutesData['simple-combo-dropdown-items-states'],
+ path: 'simple-combo-dropdown-items-states'
+ },
+ {
+ component: SimpleComboTailwindStylingComponent,
+ data: listsRoutesData['simple-combo-tailwind-styling'],
+ path: 'simple-combo-tailwind-styling'
},
{
component: SimpleComboCascadingComponent,