diff --git a/live-editing/configs/ComboConfigGenerator.ts b/live-editing/configs/ComboConfigGenerator.ts index 74e14111ee..a45aa41405 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({ 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-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/lists-routes-data.ts b/src/app/lists/lists-routes-data.ts index e2d25c0a23..c6fbc9c228 100644 --- a/src/app/lists/lists-routes-data.ts +++ b/src/app/lists/lists-routes-data.ts @@ -10,6 +10,14 @@ 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' }, + '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-main': { displayName: 'Main Simple Combo', parentName: 'Combo'}, 'simple-combo-usage': { displayName: 'Usage Simple Combo', parentName: 'Combo'}, 'simple-combo-cascading': { displayName: 'Cascading Simple Combo', parentName: 'Combo'}, diff --git a/src/app/lists/lists.routes.ts b/src/app/lists/lists.routes.ts index 3036dba6b7..86c1aaa977 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'; @@ -77,6 +85,46 @@ export const ListsRoutes: Routes = [ data: listsRoutesData['combo-overlay'], path: 'combo-overlay' }, + { + component: ComboOverviewComponent, + data: listsRoutesData['combo-overview'], + path: 'combo-overview' + }, + { + 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: SimpleComboMainComponent, data: listsRoutesData['simple-combo-main'],