From 91bb26bdd1455350755f7422f519721d72638bfb Mon Sep 17 00:00:00 2001 From: Simeon Simeonoff Date: Fri, 28 Aug 2026 17:13:24 +0300 Subject: [PATCH 1/3] feat(grids): add theming samples and grid theme playground --- live-editing/configs/GridConfigGenerator.ts | 5 + .../configs/PivotGridConfigGenerator.ts | 2 +- .../configs/ThemingConfigGenerator.ts | 16 + src/app/app.routes.server.ts | 10 + src/app/grid/grid-routes-data.ts | 1 + .../grid-theming/grid-theming.component.html | 64 ++++ .../grid-theming/grid-theming.component.scss | 145 ++++++++ .../grid-theming/grid-theming.component.ts | 53 +++ .../grid-validation-style.component.ts | 2 +- src/app/grid/grids.routes.ts | 6 + .../hgrid-rowStyles.component.ts | 4 +- .../hierarchical-grid-styling.component.html | 95 ++++-- .../hierarchical-grid-styling.component.scss | 149 +++++++- .../hierarchical-grid-styling.component.ts | 18 +- .../hierarchical-grid-styling/layout.scss | 8 +- ...rchical-grid-validation-style.component.ts | 2 +- src/app/index/index.component.scss | 12 +- .../pivot-styling.component.html | 34 +- .../pivot-styling.component.scss | 149 +++++++- .../pivot-styling/pivot-styling.component.ts | 32 +- .../grid-theme-playground/color-utils.ts | 12 + .../grid-theme-playground.component.html | 223 ++++++++++++ .../grid-theme-playground.component.scss | 173 ++++++++++ .../grid-theme-playground.component.ts | 318 ++++++++++++++++++ .../theme-color-field.component.html | 20 ++ .../theme-color-field.component.scss | 76 +++++ .../theme-color-field.component.ts | 48 +++ src/app/theming/theming-routes-data.ts | 3 +- src/app/theming/theming.routes.ts | 12 +- .../tree-grid-rowStyle.component.ts | 2 +- src/app/tree-grid/tree-grid-style/layout.scss | 6 +- .../tree-grid-style.component.html | 42 ++- .../tree-grid-style.component.scss | 149 +++++++- .../tree-grid-style.component.ts | 24 +- .../tree-grid-validation-style.component.ts | 2 +- 35 files changed, 1810 insertions(+), 107 deletions(-) create mode 100644 src/app/grid/grid-theming/grid-theming.component.html create mode 100644 src/app/grid/grid-theming/grid-theming.component.scss create mode 100644 src/app/grid/grid-theming/grid-theming.component.ts create mode 100644 src/app/theming/grid-theme-playground/color-utils.ts create mode 100644 src/app/theming/grid-theme-playground/grid-theme-playground.component.html create mode 100644 src/app/theming/grid-theme-playground/grid-theme-playground.component.scss create mode 100644 src/app/theming/grid-theme-playground/grid-theme-playground.component.ts create mode 100644 src/app/theming/grid-theme-playground/theme-color-field.component.html create mode 100644 src/app/theming/grid-theme-playground/theme-color-field.component.scss create mode 100644 src/app/theming/grid-theme-playground/theme-color-field.component.ts diff --git a/live-editing/configs/GridConfigGenerator.ts b/live-editing/configs/GridConfigGenerator.ts index 0675968292..472080951b 100644 --- a/live-editing/configs/GridConfigGenerator.ts +++ b/live-editing/configs/GridConfigGenerator.ts @@ -574,6 +574,11 @@ export class GridConfigGenerator implements IConfigGenerator { additionalFiles: ['/src/app/directives/prevent-scroll.directive.ts', '/src/app/data/customers.ts', '/src/app/grid/grid-sample-pinning-styling/layout.scss'], appConfig: BaseAppConfig })); + configs.push(new Config({ + component: 'GridThemingComponent', + additionalFiles: ['/src/app/directives/prevent-scroll.directive.ts', '/src/app/data/invoiceData.ts'], + appConfig: BaseAppConfig + })); configs.push(new Config({ component: 'GridGroupByStylingComponent', additionalFiles: ['/src/app/directives/prevent-scroll.directive.ts', '/src/app/data/invoiceData.ts', '/src/app/grid/grid-groupby-styling/layout.scss'], diff --git a/live-editing/configs/PivotGridConfigGenerator.ts b/live-editing/configs/PivotGridConfigGenerator.ts index 7f7fb25e2e..c7c720c47b 100644 --- a/live-editing/configs/PivotGridConfigGenerator.ts +++ b/live-editing/configs/PivotGridConfigGenerator.ts @@ -83,7 +83,7 @@ export class PivotGridConfigGenerator implements IConfigGenerator { })); configs.push(new Config({ - additionalFiles: ['/src/app/directives/prevent-scroll.directive.ts', '/src/app/data/dataToAnalyze.ts'], + additionalFiles: ['/src/app/data/pivot-data.ts', '/src/app/pivot-grid/pivot-styling/layout.scss'], appConfig: BaseAppConfig, component: 'PivotStylingComponent' })); diff --git a/live-editing/configs/ThemingConfigGenerator.ts b/live-editing/configs/ThemingConfigGenerator.ts index 3c0a10d09e..9d29d32b4e 100644 --- a/live-editing/configs/ThemingConfigGenerator.ts +++ b/live-editing/configs/ThemingConfigGenerator.ts @@ -72,6 +72,22 @@ export class ThemingConfigGenerator implements IConfigGenerator { shortenComponentPathBy: '/theming/angular/' })); + configs.push(new Config({ + component: 'GridThemePlaygroundComponent', + additionalFiles: [ + '/src/app/theming/grid-theme-playground/theme-color-field.component.ts', + '/src/app/theming/grid-theme-playground/theme-color-field.component.html', + '/src/app/theming/grid-theme-playground/theme-color-field.component.scss', + '/src/app/theming/grid-theme-playground/color-utils.ts', + '/src/app/data/invoiceData.ts', + '/src/app/data/pivot-data.ts', + '/src/app/data/singersData.ts', + '/src/app/tree-grid/data/employees-flat-avatars.ts' + ], + appConfig: BaseAppConfig, + shortenComponentPathBy: '/theming/grid-theme-playground/' + })); + configs.push(new Config({ component: 'TailwindSampleComponent', appConfig: BaseAppConfig, diff --git a/src/app/app.routes.server.ts b/src/app/app.routes.server.ts index 85274dd7e2..2960e62832 100644 --- a/src/app/app.routes.server.ts +++ b/src/app/app.routes.server.ts @@ -49,6 +49,16 @@ export const serverRoutes: ServerRoute[] = [ path: 'samples/lists/**', renderMode: RenderMode.Client // TODO: Fails with Prerender }, + { + // Hosts grid previews, and grids fail under Prerender like every other + // grid route below. Must precede the theming/** globs. + path: 'theming/grid-theme-playground', + renderMode: RenderMode.Client + }, + { + path: 'samples/theming/grid-theme-playground', + renderMode: RenderMode.Client + }, { path: 'theming/**', renderMode: RenderMode.Prerender diff --git a/src/app/grid/grid-routes-data.ts b/src/app/grid/grid-routes-data.ts index 57923487fe..10860e02df 100644 --- a/src/app/grid/grid-routes-data.ts +++ b/src/app/grid/grid-routes-data.ts @@ -4,6 +4,7 @@ export const gridsRoutesData = { 'grid-groupby': { displayName: 'Grid GroupBy', parentName: 'Grid' }, 'grid-cellMerge': { displayName: 'Grid Cell Merge', parentName: 'Grid' }, 'grid-cellMerge-custom': { displayName: 'Grid Cell Merge Custom', parentName: 'Grid' }, + 'grid-theming': { displayName: 'Grid Theming', parentName: 'Grid' }, 'grid-groupby-custom': {displayName: 'Grid GroupBy Custom', parentName: 'Grid'}, 'grid-groupby-paging': { displayName: 'Grid GroupBy Paging', parentName: 'Grid' }, 'grid-groupby-styling': { displayName: 'Grid GroupBy Styling', parentName: 'Grid' }, diff --git a/src/app/grid/grid-theming/grid-theming.component.html b/src/app/grid/grid-theming/grid-theming.component.html new file mode 100644 index 0000000000..6a044abecf --- /dev/null +++ b/src/app/grid/grid-theming/grid-theming.component.html @@ -0,0 +1,64 @@ +
+
+ Pick a theme + + + @for (theme of themes; track theme.class) { + + } + + +

+ Custom themes, not built-in: each is a grid-theme() with its + own background and accent. +

+
+ + + + + Invoices + + + + + + + + + + + + + + + + + + + + + +
diff --git a/src/app/grid/grid-theming/grid-theming.component.scss b/src/app/grid/grid-theming/grid-theming.component.scss new file mode 100644 index 0000000000..29578f9aa5 --- /dev/null +++ b/src/app/grid/grid-theming/grid-theming.component.scss @@ -0,0 +1,145 @@ +@use "igniteui-angular/theming" as *; + +$studio-bg: #faf4ed; +$studio-accent: #907aa9; +$ledger-bg: #eceff4; +$ledger-accent: #5e81ac; +$editorial-bg: #333c43; +$editorial-accent: #a7c080; +$midnight-bg: #282a36; +$midnight-accent: #bd93f9; + +:host { + display: block; + padding: 16px; +} + +.theme-studio { + --ig-size: var(--ig-size-large); + --ig-radius-factor: 0.6; + + @include tokens(grid-theme( + $background: $studio-bg, + $foreground: #575279, + $accent-color: $studio-accent, + $header-background: #fffaf3, + $header-border-color: #dfdad9, + $row-border-color: #f2e9e1, + $grid-border-color: #dfdad9, + $grid-shadow: (0 1px 3px rgba(87, 82, 121, 0.10), 0 1px 2px rgba(87, 82, 121, 0.06)) + )); +} + +.theme-ledger { + --ig-size: var(--ig-size-small); + --ig-radius-factor: 0; + + @include tokens(grid-theme( + $background: $ledger-bg, + $foreground: #2e3440, + $accent-color: $ledger-accent, + $header-background: #d8dee9, + $row-odd-background: #eceff4, + $row-even-background: #e5e9f0, + $body-column-border-color-odd: #d8dee9, + $body-column-border-color-even: #d8dee9, + $row-border-color: #d8dee9, + $grid-border-color: #c8d0dc + )); +} + +.theme-editorial { + --ig-size: var(--ig-size-large); + --ig-radius-factor: 0; + + @include tokens(grid-theme( + $schema: $dark-material-schema, + $background: $editorial-bg, + $foreground: #d3c6aa, + $accent-color: $editorial-accent, + $header-background: #3a464c, + $row-border-color: #333c43, + $grid-border-color: #333c43 + )); +} + +.theme-midnight { + --ig-size: var(--ig-size-medium); + --ig-radius-factor: 0.25; + + @include tokens(grid-theme( + $schema: $dark-material-schema, + $background: $midnight-bg, + $foreground: #f8f8f2, + $accent-color: $midnight-accent, + $header-background: #21222c, + $body-column-border-color-odd: #44475a, + $body-column-border-color-even: #44475a, + $row-border-color: #44475a, + $grid-border-color: #44475a + )); +} + +.grid-theming-sample { + display: flex; + flex-direction: column; + gap: 16px; +} + +.theme-picker { + --ig-button-group-elevation: 0; + + display: flex; + flex-direction: column; + gap: 6px; + align-self: flex-start; +} + +.theme-picker__label { + font-size: 12px; + font-weight: 500; + letter-spacing: 0.04em; + color: var(--ig-gray-700); +} + +.theme-picker__hint { + margin: 0; + font-size: 12px; + line-height: 1.45; + color: var(--ig-gray-600); + + code { + font-family: 'JetBrains Mono', 'Fira Code', Consolas, monospace; + font-size: 11px; + } +} + +.theme-swatch { + display: inline-block; + min-width: 14px; + aspect-ratio: 1; + border-radius: 50%; + border: 1px solid var(--ig-gray-300); + vertical-align: -2px; + background: linear-gradient(135deg, var(--swatch-bg) 0 50%, var(--swatch-accent) 50% 100%); +} + +.theme-swatch--studio { + --swatch-bg: #{$studio-bg}; + --swatch-accent: #{$studio-accent}; +} + +.theme-swatch--ledger { + --swatch-bg: #{$ledger-bg}; + --swatch-accent: #{$ledger-accent}; +} + +.theme-swatch--editorial { + --swatch-bg: #{$editorial-bg}; + --swatch-accent: #{$editorial-accent}; +} + +.theme-swatch--midnight { + --swatch-bg: #{$midnight-bg}; + --swatch-accent: #{$midnight-accent}; +} diff --git a/src/app/grid/grid-theming/grid-theming.component.ts b/src/app/grid/grid-theming/grid-theming.component.ts new file mode 100644 index 0000000000..97fe513f41 --- /dev/null +++ b/src/app/grid/grid-theming/grid-theming.component.ts @@ -0,0 +1,53 @@ +import { Component, signal } from '@angular/core'; +import { IgxButtonGroupComponent } from 'igniteui-angular/button-group'; +import { IgxButtonDirective } from 'igniteui-angular/directives'; +import { IgxNumberSummaryOperand, IgxSummaryResult } from 'igniteui-angular/core'; +import { IgxColumnComponent, IgxGridToolbarActionsComponent, IgxGridToolbarComponent, IgxGridToolbarHidingComponent, IgxGridToolbarPinningComponent, IgxGridToolbarTitleComponent } from 'igniteui-angular/grids/core'; +import { IgxGridComponent } from 'igniteui-angular/grids/grid'; +import { IgxPaginatorComponent } from 'igniteui-angular/paginator'; +import { INVOICE_DATA } from '../../data/invoiceData'; +import { IgxPreventDocumentScrollDirective } from '../../directives/prevent-scroll.directive'; +class CompactSummary extends IgxNumberSummaryOperand { + public override operate(data?: any[]): IgxSummaryResult[] { + return super.operate(data).filter(r => r.key === 'count' || r.key === 'sum'); + } +} + +@Component({ + selector: 'app-grid-theming', + styleUrls: ['./grid-theming.component.scss'], + templateUrl: './grid-theming.component.html', + imports: [ + IgxButtonDirective, + IgxButtonGroupComponent, + IgxColumnComponent, + IgxGridComponent, + IgxGridToolbarActionsComponent, + IgxGridToolbarComponent, + IgxGridToolbarHidingComponent, + IgxGridToolbarPinningComponent, + IgxGridToolbarTitleComponent, + IgxPaginatorComponent, + IgxPreventDocumentScrollDirective + ] +}) +export class GridThemingComponent { + public data = INVOICE_DATA; + public compactSummary = CompactSummary; + public themes = [ + { label: 'Studio', class: 'theme-studio', swatch: 'theme-swatch--studio' }, + { label: 'Ledger', class: 'theme-ledger', swatch: 'theme-swatch--ledger' }, + { label: 'Editorial', class: 'theme-editorial', swatch: 'theme-swatch--editorial' }, + { label: 'Midnight', class: 'theme-midnight', swatch: 'theme-swatch--midnight' } + ]; + + public activeTheme = signal(this.themes[0].class); + + public selectTheme(args: { index: number }) { + this.activeTheme.set(this.themes[args.index].class); + } + + public formatCurrency(value: number) { + return '$' + value.toFixed(2); + } +} diff --git a/src/app/grid/grid-validation-style/grid-validation-style.component.ts b/src/app/grid/grid-validation-style/grid-validation-style.component.ts index 89c22dfaed..ccf4d404d8 100644 --- a/src/app/grid/grid-validation-style/grid-validation-style.component.ts +++ b/src/app/grid/grid-validation-style/grid-validation-style.component.ts @@ -14,7 +14,7 @@ import { NgTemplateOutlet } from '@angular/common'; export class GridValidationStyleComponent { @ViewChild('grid', { read: IgxGridComponent, static: true }) public grid: IgxGridComponent; public data: any[]; - public rowStyles = { + public rowStyles: any = { background: (row: RowType) => row.validation.status === 'INVALID' ? '#FF000033' : '#00000000' }; public cellStyles = { diff --git a/src/app/grid/grids.routes.ts b/src/app/grid/grids.routes.ts index be9950dbc9..c5ded8e028 100644 --- a/src/app/grid/grids.routes.ts +++ b/src/app/grid/grids.routes.ts @@ -31,6 +31,7 @@ import { GridGroupByCustomSampleComponent } from './grid-groupby-custom-sample/g import { GridGroupByPagingSampleComponent } from './grid-group-by-paging-sample/grid-group-by-paging-sample.component'; import { GridGroupBySampleComponent } from './grid-groupby-sample/grid-groupby-sample.component'; import { GridGroupByStylingComponent } from './grid-groupby-styling/grid-groupby-styling.component'; +import { GridThemingComponent } from './grid-theming/grid-theming.component'; import { GridGroupBySummarySampleComponent } from './grid-groupby-summary-sample/grid-groupby-summary-sample.component'; import { GridGroupBySummaryStylingSampleComponent } from './grid-groupby-summary-styling-sample/grid-groupby-summary-styling-sample.component'; import { GridMovingSampleComponent } from './grid-moving-sample/grid-moving-sample.component'; @@ -168,6 +169,11 @@ export const GridsRoutes: Routes = [ data: gridsRoutesData['grid-cellMerge-custom'], path: 'grid-cellMerge-custom' }, + { + component: GridThemingComponent, + data: gridsRoutesData['grid-theming'], + path: 'grid-theming' + }, { component: GridGroupByStylingComponent, data: gridsRoutesData['grid-groupby-styling'], diff --git a/src/app/hierarchical-grid/hgrid-rowStyle-sample/hgrid-rowStyles.component.ts b/src/app/hierarchical-grid/hgrid-rowStyle-sample/hgrid-rowStyles.component.ts index ae92b4259b..cb950623cf 100644 --- a/src/app/hierarchical-grid/hgrid-rowStyle-sample/hgrid-rowStyles.component.ts +++ b/src/app/hierarchical-grid/hgrid-rowStyle-sample/hgrid-rowStyles.component.ts @@ -18,12 +18,12 @@ export class HGridRowStylesSampleComponent implements OnInit { private hierarchicalGrid: IgxHierarchicalGridComponent; public localdata; - public rowStyles = { + public rowStyles: any = { background:(row: RowType) => row.data['HasGrammyAward'] ? '#eeddd3' : '#f0efeb', 'border-left': (row: RowType) => row.data['HasGrammyAward'] ? '2px solid #dda15e' : null }; - public childRowStyles = { + public childRowStyles: any = { 'border-left': (row: RowType) => row.data['BillboardReview'] > 70 ? '3.5px solid #dda15e' : null }; constructor() { } diff --git a/src/app/hierarchical-grid/hierarchical-grid-styling/hierarchical-grid-styling.component.html b/src/app/hierarchical-grid/hierarchical-grid-styling/hierarchical-grid-styling.component.html index 7028f9adae..8f27753f0c 100644 --- a/src/app/hierarchical-grid/hierarchical-grid-styling/hierarchical-grid-styling.component.html +++ b/src/app/hierarchical-grid/hierarchical-grid-styling/hierarchical-grid-styling.component.html @@ -1,36 +1,67 @@
- - - - -
- -
-
-
- - - +
+ Pick a theme - - - - - - - - - - - - + + @for (theme of themes; track theme.class) { + + } + - - - - - - - +

+ Custom themes, not built-in: each is a grid-theme() with its + own background and accent. +

+
+ + + + + +
+ +
+
+
+ + + + + + + + + + + + + + + + + + + + + + + +
diff --git a/src/app/hierarchical-grid/hierarchical-grid-styling/hierarchical-grid-styling.component.scss b/src/app/hierarchical-grid/hierarchical-grid-styling/hierarchical-grid-styling.component.scss index 2c0d35a3be..9a14ff29e1 100644 --- a/src/app/hierarchical-grid/hierarchical-grid-styling/hierarchical-grid-styling.component.scss +++ b/src/app/hierarchical-grid/hierarchical-grid-styling/hierarchical-grid-styling.component.scss @@ -1,16 +1,141 @@ @use "layout.scss"; @use "igniteui-angular/theming" as *; -$background: #292826; -$foreground: #eeece1; -$accent: #ffcd0f; - -$custom-grid: grid-theme( - $background: $background, - $foreground: $foreground, - $accent-color: $accent, -); - -:host { - @include tokens($custom-grid); +$studio-bg: #faf4ed; +$studio-accent: #907aa9; +$ledger-bg: #eceff4; +$ledger-accent: #5e81ac; +$editorial-bg: #333c43; +$editorial-accent: #a7c080; +$midnight-bg: #282a36; +$midnight-accent: #bd93f9; + +.theme-studio { + --ig-size: var(--ig-size-large); + --ig-radius-factor: 0.6; + + @include tokens(grid-theme( + $background: $studio-bg, + $foreground: #575279, + $accent-color: $studio-accent, + $header-background: #fffaf3, + $header-border-color: #dfdad9, + $row-border-color: #f2e9e1, + $grid-border-color: #dfdad9, + $grid-shadow: (0 1px 3px rgba(87, 82, 121, 0.10), 0 1px 2px rgba(87, 82, 121, 0.06)) + )); +} + +.theme-ledger { + --ig-size: var(--ig-size-small); + --ig-radius-factor: 0; + + @include tokens(grid-theme( + $background: $ledger-bg, + $foreground: #2e3440, + $accent-color: $ledger-accent, + $header-background: #d8dee9, + $row-odd-background: #eceff4, + $row-even-background: #e5e9f0, + $body-column-border-color-odd: #d8dee9, + $body-column-border-color-even: #d8dee9, + $row-border-color: #d8dee9, + $grid-border-color: #c8d0dc + )); +} + +.theme-editorial { + --ig-size: var(--ig-size-large); + --ig-radius-factor: 0; + + @include tokens(grid-theme( + $schema: $dark-material-schema, + $background: $editorial-bg, + $foreground: #d3c6aa, + $accent-color: $editorial-accent, + $header-background: #3a464c, + $row-border-color: #333c43, + $grid-border-color: #333c43 + )); +} + +.theme-midnight { + --ig-size: var(--ig-size-medium); + --ig-radius-factor: 0.25; + + @include tokens(grid-theme( + $schema: $dark-material-schema, + $background: $midnight-bg, + $foreground: #f8f8f2, + $accent-color: $midnight-accent, + $header-background: #21222c, + $body-column-border-color-odd: #44475a, + $body-column-border-color-even: #44475a, + $row-border-color: #44475a, + $grid-border-color: #44475a + )); +} + +.grid__wrapper { + display: flex; + flex-direction: column; + gap: 16px; +} + +.theme-picker { + --ig-button-group-elevation: 0; + + display: flex; + flex-direction: column; + gap: 6px; + align-self: flex-start; +} + +.theme-picker__label { + font-size: 12px; + font-weight: 500; + letter-spacing: 0.04em; + color: var(--ig-gray-700); +} + +.theme-picker__hint { + margin: 0; + font-size: 12px; + line-height: 1.45; + color: var(--ig-gray-600); + + code { + font-family: 'JetBrains Mono', 'Fira Code', Consolas, monospace; + font-size: 11px; + } +} + +.theme-swatch { + display: inline-block; + min-width: 14px; + aspect-ratio: 1; + border-radius: 50%; + border: 1px solid var(--ig-gray-300); + vertical-align: -2px; + background: linear-gradient(135deg, var(--swatch-bg) 0 50%, var(--swatch-accent) 50% 100%); +} + +.theme-swatch--studio { + --swatch-bg: #{$studio-bg}; + --swatch-accent: #{$studio-accent}; +} + +.theme-swatch--ledger { + --swatch-bg: #{$ledger-bg}; + --swatch-accent: #{$ledger-accent}; +} + +.theme-swatch--editorial { + --swatch-bg: #{$editorial-bg}; + --swatch-accent: #{$editorial-accent}; +} + +.theme-swatch--midnight { + --swatch-bg: #{$midnight-bg}; + --swatch-accent: #{$midnight-accent}; } diff --git a/src/app/hierarchical-grid/hierarchical-grid-styling/hierarchical-grid-styling.component.ts b/src/app/hierarchical-grid/hierarchical-grid-styling/hierarchical-grid-styling.component.ts index fcc915ddfc..91bd07fa4c 100644 --- a/src/app/hierarchical-grid/hierarchical-grid-styling/hierarchical-grid-styling.component.ts +++ b/src/app/hierarchical-grid/hierarchical-grid-styling/hierarchical-grid-styling.component.ts @@ -1,4 +1,6 @@ -import { Component } from '@angular/core'; +import { Component, signal } from '@angular/core'; +import { IgxButtonGroupComponent } from 'igniteui-angular/button-group'; +import { IgxButtonDirective } from 'igniteui-angular/directives'; import { IgxCellTemplateDirective, IgxColumnComponent } from 'igniteui-angular/grids/core'; import { IgxHierarchicalGridComponent, IgxRowIslandComponent } from 'igniteui-angular/grids/hierarchical-grid'; import { SINGERS } from '../../data/singersData'; @@ -8,7 +10,7 @@ import { IgxPreventDocumentScrollDirective } from '../../directives/prevent-scro selector: 'app-hierarchical-grid-styling', styleUrls: ['./hierarchical-grid-styling.component.scss'], templateUrl: 'hierarchical-grid-styling.component.html', - imports: [IgxHierarchicalGridComponent, IgxPreventDocumentScrollDirective, IgxColumnComponent, IgxCellTemplateDirective, IgxRowIslandComponent] + imports: [IgxHierarchicalGridComponent, IgxPreventDocumentScrollDirective, IgxColumnComponent, IgxCellTemplateDirective, IgxRowIslandComponent, IgxButtonGroupComponent, IgxButtonDirective] }) export class HGridStylingComponent { @@ -16,11 +18,23 @@ export class HGridStylingComponent { public col: IgxColumnComponent; public pWidth: string; public nWidth: string; + public themes = [ + { label: 'Studio', class: 'theme-studio', swatch: 'theme-swatch--studio' }, + { label: 'Ledger', class: 'theme-ledger', swatch: 'theme-swatch--ledger' }, + { label: 'Editorial', class: 'theme-editorial', swatch: 'theme-swatch--editorial' }, + { label: 'Midnight', class: 'theme-midnight', swatch: 'theme-swatch--midnight' } + ]; + + public activeTheme = signal(this.themes[0].class); constructor() { this.localdata = SINGERS; } + public selectTheme(args: { index: number }) { + this.activeTheme.set(this.themes[args.index].class); + } + public onResize(event) { this.col = event.column; this.pWidth = event.prevWidth; diff --git a/src/app/hierarchical-grid/hierarchical-grid-styling/layout.scss b/src/app/hierarchical-grid/hierarchical-grid-styling/layout.scss index b620c7ade6..e2bcf0bd0e 100644 --- a/src/app/hierarchical-grid/hierarchical-grid-styling/layout.scss +++ b/src/app/hierarchical-grid/hierarchical-grid-styling/layout.scss @@ -1,12 +1,18 @@ +// Fits the smallest row density. .photo { vertical-align: middle; - max-height: 62px; + max-block-size: 24px; + border-radius: 50%; } .cell__inner_2 { margin: 1px } +:host { + display: block; +} + .grid__wrapper{ padding: 10px; } diff --git a/src/app/hierarchical-grid/hierarchical-grid-validation-style/hierarchical-grid-validation-style.component.ts b/src/app/hierarchical-grid/hierarchical-grid-validation-style/hierarchical-grid-validation-style.component.ts index 49871eb70b..ffc614547d 100644 --- a/src/app/hierarchical-grid/hierarchical-grid-validation-style/hierarchical-grid-validation-style.component.ts +++ b/src/app/hierarchical-grid/hierarchical-grid-validation-style/hierarchical-grid-validation-style.component.ts @@ -17,7 +17,7 @@ export class HGridValidationStyleComponent { @ViewChild('hierarchicalGrid', { read: IgxHierarchicalGridComponent, static: true }) public hierarchicalGrid: IgxHierarchicalGridComponent; @ViewChild('childGrid', { static: true }) private childGrid: IgxRowIslandComponent; public localData: Singer[]; - public rowStyles = { + public rowStyles: any = { background: (row: RowType) => row.validation.status === 'INVALID' ? '#FF000033' : '#00000000' }; public cellStyles = { diff --git a/src/app/index/index.component.scss b/src/app/index/index.component.scss index f6cb2bd6a5..5b2502768b 100644 --- a/src/app/index/index.component.scss +++ b/src/app/index/index.component.scss @@ -3,19 +3,21 @@ } .main { + display: flex; + flex-direction: column; width: calc(100% - 300px); + height: 100%; } -.content { - width: 100%; - // height: calc(100% - 56px); - height: 100%; +.content { + width: 100%; + flex: 1 1 auto; max-height: 1000px; min-width: 300px; min-height: 300px; justify-content: center; align-items: stretch; - overflow: auto; + overflow: auto; } .navdrawer-ellipsis { diff --git a/src/app/pivot-grid/pivot-styling/pivot-styling.component.html b/src/app/pivot-grid/pivot-styling/pivot-styling.component.html index 08df70c02f..f94ecf55aa 100644 --- a/src/app/pivot-grid/pivot-styling/pivot-styling.component.html +++ b/src/app/pivot-grid/pivot-styling/pivot-styling.component.html @@ -1,2 +1,32 @@ - - \ No newline at end of file +
+
+ Pick a theme + + + @for (theme of themes; track theme.class) { + + } + + +

+ Custom themes, not built-in: each is a grid-theme() with its + own background and accent. +

+
+ + + +
diff --git a/src/app/pivot-grid/pivot-styling/pivot-styling.component.scss b/src/app/pivot-grid/pivot-styling/pivot-styling.component.scss index 238acf9733..d703a350ad 100644 --- a/src/app/pivot-grid/pivot-styling/pivot-styling.component.scss +++ b/src/app/pivot-grid/pivot-styling/pivot-styling.component.scss @@ -1,16 +1,141 @@ @use "layout.scss"; @use "igniteui-angular/theming" as *; -$background: #292826; -$foreground: #eeece1; -$accent: #ffcd0f; - -$custom-grid: grid-theme( - $background: $background, - $foreground: $foreground, - $accent-color: $accent, -); - -igx-pivot-grid { - @include tokens($custom-grid); +$studio-bg: #faf4ed; +$studio-accent: #907aa9; +$ledger-bg: #eceff4; +$ledger-accent: #5e81ac; +$editorial-bg: #333c43; +$editorial-accent: #a7c080; +$midnight-bg: #282a36; +$midnight-accent: #bd93f9; + +.theme-studio { + --ig-size: var(--ig-size-large); + --ig-radius-factor: 0.6; + + @include tokens(grid-theme( + $background: $studio-bg, + $foreground: #575279, + $accent-color: $studio-accent, + $header-background: #fffaf3, + $header-border-color: #dfdad9, + $row-border-color: #f2e9e1, + $grid-border-color: #dfdad9, + $grid-shadow: (0 1px 3px rgba(87, 82, 121, 0.10), 0 1px 2px rgba(87, 82, 121, 0.06)) + )); +} + +.theme-ledger { + --ig-size: var(--ig-size-small); + --ig-radius-factor: 0; + + @include tokens(grid-theme( + $background: $ledger-bg, + $foreground: #2e3440, + $accent-color: $ledger-accent, + $header-background: #d8dee9, + $row-odd-background: #eceff4, + $row-even-background: #e5e9f0, + $body-column-border-color-odd: #d8dee9, + $body-column-border-color-even: #d8dee9, + $row-border-color: #d8dee9, + $grid-border-color: #c8d0dc + )); +} + +.theme-editorial { + --ig-size: var(--ig-size-large); + --ig-radius-factor: 0; + + @include tokens(grid-theme( + $schema: $dark-material-schema, + $background: $editorial-bg, + $foreground: #d3c6aa, + $accent-color: $editorial-accent, + $header-background: #3a464c, + $row-border-color: #333c43, + $grid-border-color: #333c43 + )); +} + +.theme-midnight { + --ig-size: var(--ig-size-medium); + --ig-radius-factor: 0.25; + + @include tokens(grid-theme( + $schema: $dark-material-schema, + $background: $midnight-bg, + $foreground: #f8f8f2, + $accent-color: $midnight-accent, + $header-background: #21222c, + $body-column-border-color-odd: #44475a, + $body-column-border-color-even: #44475a, + $row-border-color: #44475a, + $grid-border-color: #44475a + )); +} + +.pivot-theming-sample { + display: flex; + flex-direction: column; + gap: 16px; +} + +.theme-picker { + --ig-button-group-elevation: 0; + + display: flex; + flex-direction: column; + gap: 6px; + align-self: flex-start; +} + +.theme-picker__label { + font-size: 12px; + font-weight: 500; + letter-spacing: 0.04em; + color: var(--ig-gray-700); +} + +.theme-picker__hint { + margin: 0; + font-size: 12px; + line-height: 1.45; + color: var(--ig-gray-600); + + code { + font-family: 'JetBrains Mono', 'Fira Code', Consolas, monospace; + font-size: 11px; + } +} + +.theme-swatch { + display: inline-block; + min-width: 14px; + aspect-ratio: 1; + border-radius: 50%; + border: 1px solid var(--ig-gray-300); + vertical-align: -2px; + background: linear-gradient(135deg, var(--swatch-bg) 0 50%, var(--swatch-accent) 50% 100%); +} + +.theme-swatch--studio { + --swatch-bg: #{$studio-bg}; + --swatch-accent: #{$studio-accent}; +} + +.theme-swatch--ledger { + --swatch-bg: #{$ledger-bg}; + --swatch-accent: #{$ledger-accent}; +} + +.theme-swatch--editorial { + --swatch-bg: #{$editorial-bg}; + --swatch-accent: #{$editorial-accent}; +} + +.theme-swatch--midnight { + --swatch-bg: #{$midnight-bg}; + --swatch-accent: #{$midnight-accent}; } diff --git a/src/app/pivot-grid/pivot-styling/pivot-styling.component.ts b/src/app/pivot-grid/pivot-styling/pivot-styling.component.ts index b85a70c4fe..039ae0185f 100644 --- a/src/app/pivot-grid/pivot-styling/pivot-styling.component.ts +++ b/src/app/pivot-grid/pivot-styling/pivot-styling.component.ts @@ -1,6 +1,8 @@ -import { Component, ChangeDetectionStrategy } from "@angular/core"; +import { Component, ChangeDetectionStrategy, signal } from "@angular/core"; import { DATA } from '../../data/pivot-data'; +import { IgxButtonGroupComponent } from 'igniteui-angular/button-group'; +import { IgxButtonDirective } from 'igniteui-angular/directives'; import { IPivotConfiguration, IgxPivotNumericAggregate } from 'igniteui-angular/grids/core'; import { IgxPivotGridComponent } from 'igniteui-angular/grids/pivot-grid'; @@ -9,24 +11,38 @@ import { IgxPivotGridComponent } from 'igniteui-angular/grids/pivot-grid'; styleUrls: ['./pivot-styling.component.scss'], templateUrl: './pivot-styling.component.html', changeDetection: ChangeDetectionStrategy.Eager, - imports: [IgxPivotGridComponent] + imports: [IgxPivotGridComponent, IgxButtonGroupComponent, IgxButtonDirective] }) export class PivotStylingComponent { public data = DATA; + public themes = [ + { label: 'Studio', class: 'theme-studio', swatch: 'theme-swatch--studio' }, + { label: 'Ledger', class: 'theme-ledger', swatch: 'theme-swatch--ledger' }, + { label: 'Editorial', class: 'theme-editorial', swatch: 'theme-swatch--editorial' }, + { label: 'Midnight', class: 'theme-midnight', swatch: 'theme-swatch--midnight' } + ]; + + public activeTheme = signal(this.themes[0].class); + public pivotConfigHierarchy: IPivotConfiguration = { columns: [ { memberName: 'Product', memberFunction: (data) => data.Product.Name, enabled: true - } + } ], rows: [ { - memberName: 'Seller', - memberFunction: (data) => data.Seller.Name, - enabled: true + memberName: 'City', + memberFunction: (data) => data.Seller.City, + enabled: true, + childLevel: { + memberName: 'Seller', + memberFunction: (data) => data.Seller.Name, + enabled: true + } } ], values: [ @@ -42,4 +58,8 @@ export class PivotStylingComponent { ], filters: null }; + + public selectTheme(args: { index: number }) { + this.activeTheme.set(this.themes[args.index].class); + } } diff --git a/src/app/theming/grid-theme-playground/color-utils.ts b/src/app/theming/grid-theme-playground/color-utils.ts new file mode 100644 index 0000000000..9c3ed49e08 --- /dev/null +++ b/src/app/theming/grid-theme-playground/color-utils.ts @@ -0,0 +1,12 @@ +/** Normalizes free-typed color text to a 6-digit uppercase hex string, or null if invalid. */ +export function normalizeHexColor(raw: string): string | null { + let value = raw.trim(); + if (!value) return null; + if (!value.startsWith('#')) value = '#' + value; + + if (/^#[0-9a-fA-F]{3}$/.test(value)) { + value = '#' + value[1] + value[1] + value[2] + value[2] + value[3] + value[3]; + } + + return /^#[0-9a-fA-F]{6}$/.test(value) ? value.toUpperCase() : null; +} diff --git a/src/app/theming/grid-theme-playground/grid-theme-playground.component.html b/src/app/theming/grid-theme-playground/grid-theme-playground.component.html new file mode 100644 index 0000000000..fb39188573 --- /dev/null +++ b/src/app/theming/grid-theme-playground/grid-theme-playground.component.html @@ -0,0 +1,223 @@ +
+ +
+
+ + @switch (preview()) { + @case ('grid') { + + + + + + + + + } + @case ('tree') { + @defer (on immediate) { + + + +
+ + {{ cell.value }} +
+
+
+ + + +
+ } @placeholder { +
Loading preview…
+ } + } + @case ('hierarchical') { + @defer (on immediate) { + + + + + + + + + + + + } @placeholder { +
Loading preview…
+ } + } + @case ('pivot') { + @defer (on immediate) { + + + } @placeholder { +
Loading preview…
+ } + } + } +
+
+
+ + + +
Theme code
+
+ +
+ + + + SCSS + + + +
+
+
+ + + + CSS variables + + + +
+
+
+
+
+ +
+ +
+
diff --git a/src/app/theming/grid-theme-playground/grid-theme-playground.component.scss b/src/app/theming/grid-theme-playground/grid-theme-playground.component.scss new file mode 100644 index 0000000000..2a0e5ce636 --- /dev/null +++ b/src/app/theming/grid-theme-playground/grid-theme-playground.component.scss @@ -0,0 +1,173 @@ +@use "igniteui-angular/theming" as *; + +:host { + display: block; + block-size: 100%; +} + +.playground { + display: flex; + align-items: stretch; + gap: 20px; + padding: 16px; + box-sizing: border-box; + block-size: 100%; + min-block-size: 520px; + overflow: hidden; +} + +.playground__stage { + @include tokens(grid-theme( + $background: #08002e, + $accent-color: #ff7d52 + )); +} + +.playground__rail { + --ig-size: var(--ig-size-medium); + --ig-button-group-elevation: 0; + + flex: 0 0 280px; + display: flex; + flex-direction: column; + gap: 12px; + min-block-size: 0; + overflow-y: auto; + padding-inline-end: 4px; + + igx-expansion-panel-title { + font-size: 13px; + font-weight: 600; + letter-spacing: 0.04em; + color: var(--ig-gray-700); + } +} + +.playground__hint { + margin: 0 0 10px; + font-size: 12px; + line-height: 1.45; + color: var(--ig-gray-600); +} + +.playground__fields { + display: flex; + flex-direction: column; + gap: 14px; +} + +.playground__control { + display: flex; + flex-direction: column; + gap: 6px; +} + +.playground__label { + font-size: 12px; + color: var(--ig-gray-700); +} + +.playground__actions { + position: sticky; + inset-block-end: 0; + z-index: 1; + display: flex; + gap: 8px; + padding-block: 8px; + background: var(--ig-surface-500); + border-block-start: 1px solid var(--ig-gray-200); + + button { + flex: 1; + } +} + +.playground__dialog-title { + font-size: 16px; + font-weight: 600; +} + +.playground__dialog-body { + --ig-size: var(--ig-size-medium); + --ig-icon-size: 18px; + --ig-h5-font-size: 14px; + --ig-h5-font-weight: 600; + --ig-expansion-panel-border-radius: 4px; + --ig-expansion-panel-header-background: var(--ig-gray-100); + + inline-size: min(680px, 80vw); + + ::ng-deep .igx-expansion-panel__body { + padding: 0; + } +} + +.playground__copy { + margin-inline-start: auto; +} + +.playground__export { + border: 1px solid var(--ig-gray-300); + border-radius: 6px; + overflow: hidden; +} + +.playground__code { + ::ng-deep pre.shiki { + margin: 0; + padding: 10px 12px; + font-size: 13px; + line-height: 1.5; + font-family: 'JetBrains Mono', 'Fira Code', Consolas, monospace; + max-block-size: 55vh; + overflow: auto; + overscroll-behavior: contain; + } +} + +.playground__loading { + display: grid; + place-items: center; + block-size: 100%; + color: var(--ig-gray-600); + font-size: 13px; +} + +.playground__preview { + flex: 1 1 auto; + min-inline-size: 0; + min-block-size: 0; + display: flex; + flex-direction: column; + gap: 12px; +} + +.playground__stage { + flex: 1 1 auto; + min-block-size: 0; + overflow: hidden; +} + +.playground__cell { + display: flex; + align-items: center; + gap: 8px; + height: 100%; +} + +@media (max-width: 860px) { + .playground { + flex-direction: column; + block-size: auto; + overflow: visible; + } + + .playground__rail { + flex: 0 0 auto; + overflow: visible; + } + + .playground__stage { + min-block-size: 460px; + } +} diff --git a/src/app/theming/grid-theme-playground/grid-theme-playground.component.ts b/src/app/theming/grid-theme-playground/grid-theme-playground.component.ts new file mode 100644 index 0000000000..f16a90f90d --- /dev/null +++ b/src/app/theming/grid-theme-playground/grid-theme-playground.component.ts @@ -0,0 +1,318 @@ +import { Component, ElementRef, afterNextRender, computed, inject, signal, viewChild } from '@angular/core'; +import { DomSanitizer, SafeHtml } from '@angular/platform-browser'; +import { IgxAccordionComponent } from 'igniteui-angular/accordion'; +import { IgxAvatarComponent } from 'igniteui-angular/avatar'; +import { IgxButtonGroupComponent } from 'igniteui-angular/button-group'; +import { IgxNumberSummaryOperand, IgxSummaryResult } from 'igniteui-angular/core'; +import { IgxIconComponent } from 'igniteui-angular/icon'; +import { IgxDialogActionsDirective, IgxDialogComponent, IgxDialogTitleDirective } from 'igniteui-angular/dialog'; +import { IgxButtonDirective, IgxIconButtonDirective } from 'igniteui-angular/directives'; +import { IGX_EXPANSION_PANEL_DIRECTIVES } from 'igniteui-angular/expansion-panel'; +import { IgxCellTemplateDirective, IgxColumnComponent, IPivotConfiguration, IgxPivotNumericAggregate } from 'igniteui-angular/grids/core'; +import { IgxGridComponent } from 'igniteui-angular/grids/grid'; +import { IgxHierarchicalGridComponent, IgxRowIslandComponent } from 'igniteui-angular/grids/hierarchical-grid'; +import { IgxPivotGridComponent } from 'igniteui-angular/grids/pivot-grid'; +import { IgxTreeGridComponent } from 'igniteui-angular/grids/tree-grid'; +import { IgxPaginatorComponent } from 'igniteui-angular/paginator'; +import { IgxSwitchComponent } from 'igniteui-angular/switch'; +import { INVOICE_DATA } from '../../data/invoiceData'; +import { DATA as PIVOT_DATA } from '../../data/pivot-data'; +import { SINGERS } from '../../data/singersData'; +import { EMPLOYEE_FLAT_AVATARS_DATA } from '../../tree-grid/data/employees-flat-avatars'; +import { ThemeColorFieldComponent } from './theme-color-field.component'; + +type TokenKey = 'background' | 'accentColor' | 'foreground' | 'headerBackground' | 'headerForeground'; + +interface TokenDescriptor { + key: TokenKey; + cssVar: string; + scss: string; + label: string; +} +class CompactSummary extends IgxNumberSummaryOperand { + public override operate(data?: any[]): IgxSummaryResult[] { + return super.operate(data).filter(r => r.key === 'count' || r.key === 'sum'); + } +} +const PRIMARY_TOKENS: TokenDescriptor[] = [ + { key: 'background', cssVar: '--ig-grid-background', scss: '$background', label: 'Background' }, + { key: 'accentColor', cssVar: '--ig-grid-accent-color', scss: '$accent-color', label: 'Accent' }, + { key: 'foreground', cssVar: '--ig-grid-foreground', scss: '$foreground', label: 'Foreground' } +]; +const HEADER_TOKENS: TokenDescriptor[] = [ + { key: 'headerBackground', cssVar: '--ig-grid-header-background', scss: '$header-background', label: 'Header background' }, + { key: 'headerForeground', cssVar: '--ig-grid-header-text-color', scss: '$header-text-color', label: 'Header foreground' } +]; + +const ALL_TOKENS = [...PRIMARY_TOKENS, ...HEADER_TOKENS]; + +const EMPTY_COLORS: Record = { + background: '', accentColor: '', foreground: '', headerBackground: '', headerForeground: '' +}; +/** What Sass emits for header text once $header-background is declared. */ +const HEADER_TEXT_ON_HEADER = 'hsla(from color(from var(--ig-grid-header-background) var(--y-contrast)) h 0 l/1)'; +const DIVIDER = 'hsl(from color-mix(in srgb, var(--ig-grid-foreground) 16%, var(--ig-grid-background)) h s l/0.38)'; +const ZEBRA = 'color-mix(in srgb, var(--ig-grid-foreground) 4%, var(--ig-grid-background))'; +const NO_DIVIDER = 'var(--ig-grid-background)'; + +@Component({ + selector: 'app-grid-theme-playground', + styleUrls: ['./grid-theme-playground.component.scss'], + templateUrl: './grid-theme-playground.component.html', + imports: [ + IgxAccordionComponent, IGX_EXPANSION_PANEL_DIRECTIVES, IgxAvatarComponent, IgxButtonDirective, + IgxButtonGroupComponent, IgxCellTemplateDirective, IgxColumnComponent, + IgxDialogActionsDirective, IgxDialogComponent, IgxDialogTitleDirective, IgxGridComponent, + IgxHierarchicalGridComponent, IgxIconButtonDirective, IgxIconComponent, IgxPaginatorComponent, IgxPivotGridComponent, IgxRowIslandComponent, + IgxSwitchComponent, IgxTreeGridComponent, ThemeColorFieldComponent + ] +}) +export class GridThemePlaygroundComponent { + protected readonly primaryTokens = PRIMARY_TOKENS; + protected readonly headerTokens = HEADER_TOKENS; + protected readonly compactSummary = CompactSummary; + + protected readonly colors = signal>({ ...EMPTY_COLORS }); + protected readonly size = signal<'small' | 'medium' | 'large'>('medium'); + protected readonly radiusFactor = signal(0.4); + protected readonly horizontalDividers = signal(true); + protected readonly verticalDividers = signal(false); + protected readonly zebra = signal(false); + protected readonly preview = signal<'grid' | 'tree' | 'hierarchical' | 'pivot'>('grid'); + + protected readonly copied = signal<'scss' | 'css' | null>(null); + + protected readonly sizes = [ + { label: 'Small', selected: false }, + { label: 'Medium', selected: true }, + { label: 'Large', selected: false } + ]; + protected readonly roundnessOptions = [ + { label: 'Square', selected: false }, + { label: 'Soft', selected: true }, + { label: 'Round', selected: false } + ]; + protected readonly previews = [ + { label: 'Flat', selected: true }, + { label: 'Tree', selected: false }, + { label: 'Hierarchical', selected: false }, + { label: 'Pivot', selected: false } + ]; + + public readonly data = INVOICE_DATA; + public readonly treeData = EMPLOYEE_FLAT_AVATARS_DATA(); + public readonly hierarchicalData = SINGERS; + public readonly pivotData = PIVOT_DATA; + + public pivotConfig: IPivotConfiguration = { + columns: [{ memberName: 'Product', memberFunction: (data) => data.Product.Name, enabled: true }], + rows: [{ + memberName: 'City', + memberFunction: (data) => data.Seller.City, + enabled: true, + childLevel: { memberName: 'Seller', memberFunction: (data) => data.Seller.Name, enabled: true } + }], + values: [{ + member: 'NumberOfUnits', + aggregate: { aggregator: IgxPivotNumericAggregate.sum, key: 'sum', label: 'Sum' }, + enabled: true + }], + filters: null + }; + + private readonly sanitizer = inject(DomSanitizer); + + protected readonly scssHtml = signal(null); + protected readonly cssHtml = signal(null); + + private highlighter: { codeToHtml: (code: string, opts: { lang: string; theme: string }) => string } | null = null; + + private readonly stage = viewChild.required>('stage'); + /** The compiled theme, read from the stylesheet: the CSS export needs every + * token, not just the roots, to work in an app with no grid-theme(). */ + private readonly themeTokens = signal>([]); + + constructor() { + afterNextRender(() => { + this.seedFromStylesheet(); + this.readCompiledTheme(); + }); + } + protected readonly previewStyle = computed>(() => { + const style: Record = {}; + const colors = this.colors(); + + for (const token of ALL_TOKENS) { + if (colors[token.key]) { + style[token.cssVar] = colors[token.key]; + } + } + if (colors.headerBackground && !colors.headerForeground) { + style['--ig-grid-header-text-color'] = HEADER_TEXT_ON_HEADER; + } + + style['--ig-size'] = `var(--ig-size-${this.size()})`; + style['--ig-radius-factor'] = `${this.radiusFactor()}`; + style['--ig-grid-row-border-color'] = this.horizontalDividers() ? DIVIDER : NO_DIVIDER; + + const columnRule = this.verticalDividers() ? DIVIDER : NO_DIVIDER; + style['--ig-grid-body-column-border-color-odd'] = columnRule; + style['--ig-grid-body-column-border-color-even'] = columnRule; + + if (this.zebra()) { + style['--ig-grid-row-even-background'] = ZEBRA; + } + + return style; + }); + protected readonly exportScss = computed(() => { + const colors = this.colors(); + const themeLines = ALL_TOKENS + .filter(token => colors[token.key]) + .map(token => ` ${token.scss}: ${colors[token.key]},`); + + if (!this.horizontalDividers()) { + themeLines.push(` $row-border-color: ${NO_DIVIDER},`); + } + if (this.verticalDividers()) { + themeLines.push(` $body-column-border-color-odd: ${DIVIDER},`); + themeLines.push(` $body-column-border-color-even: ${DIVIDER},`); + } + if (this.zebra()) { + themeLines.push(` $row-even-background: ${ZEBRA},`); + } + + const varLines = [ + ` --ig-size: var(--ig-size-${this.size()});`, + ` --ig-radius-factor: ${this.radiusFactor()};` + ]; + + if (!themeLines.length) { + return `.my-grid {\n${varLines.join('\n')}\n}`; + } + + return `.my-grid {\n${varLines.join('\n')}\n\n @include tokens(grid-theme(\n${themeLines.join('\n')}\n ));\n}`; + }); + + protected readonly exportCss = computed(() => { + const overrides = this.previewStyle(); + const base = this.themeTokens(); + + if (!base.length) { + const lines = Object.entries(overrides).map(([name, value]) => ` ${name}: ${value};`); + return `/* Overrides only -- requires a grid-theme() base to derive from. */\n.my-grid {\n${lines.join('\n')}\n}`; + } + + const emitted = new Set(); + const lines = base.map(([name, value]) => { + emitted.add(name); + return ` ${name}: ${overrides[name] ?? value};`; + }); + const extras = Object.entries(overrides) + .filter(([name]) => !emitted.has(name)) + .map(([name, value]) => ` ${name}: ${value};`); + + return `.my-grid {\n${[...extras, ...lines].join('\n')}\n}`; + }); + + protected setColor(key: TokenKey, value: string): void { + this.colors.update(current => ({ ...current, [key]: value })); + } + + protected reset(): void { + this.colors.set({ ...EMPTY_COLORS }); + this.size.set('medium'); + this.radiusFactor.set(0.4); + this.horizontalDividers.set(true); + this.verticalDividers.set(false); + this.zebra.set(false); + this.seedFromStylesheet(); + } + + protected async showCode(dialog: IgxDialogComponent): Promise { + // Loaded on demand so shiki stays out of the initial bundle. + if (!this.highlighter) { + // shiki/core with explicit grammars: the bundled entry point resolves + // languages dynamically and emits a chunk for every one it ships. + const [core, engine, scss, css, theme] = await Promise.all([ + import('shiki/core'), + import('shiki/engine/javascript'), + import('shiki/langs/scss.mjs'), + import('shiki/langs/css.mjs'), + import('shiki/themes/material-theme-lighter.mjs') + ]); + + this.highlighter = await core.createHighlighterCore({ + themes: [theme.default], + langs: [scss.default, css.default], + engine: engine.createJavaScriptRegexEngine() + }); + } + + const render = (code: string | null, lang: string) => code + ? this.sanitizer.bypassSecurityTrustHtml(this.highlighter.codeToHtml(code, { lang, theme: 'material-theme-lighter' })) + : null; + + this.scssHtml.set(render(this.exportScss(), 'scss')); + this.cssHtml.set(render(this.exportCss(), 'css')); + dialog.open(); + } + + protected copy(code: string | null, format: 'scss' | 'css'): void { + if (!code) return; + navigator.clipboard.writeText(code).then(() => { + this.copied.set(format); + setTimeout(() => this.copied.set(null), 2000); + }); + } + + protected onSize(args: { index: number }): void { + this.size.set((['small', 'medium', 'large'] as const)[args.index]); + } + + protected onRoundness(args: { index: number }): void { + this.radiusFactor.set([0, 0.4, 1][args.index]); + } + + protected onPreview(args: { index: number }): void { + this.preview.set((['grid', 'tree', 'hierarchical', 'pivot'] as const)[args.index]); + } + private readCompiledTheme(): void { + const tokens: Array<[string, string]> = []; + + for (const sheet of Array.from(document.styleSheets)) { + let rules: CSSRuleList; + + try { + rules = sheet.cssRules; + } catch { + continue; // cross-origin sheet, not ours + } + + for (const rule of Array.from(rules)) { + if (!(rule instanceof CSSStyleRule) || !rule.selectorText.includes('playground__stage')) { + continue; + } + + for (let i = 0; i < rule.style.length; i++) { + const name = rule.style.item(i); + if (name.startsWith('--ig-')) { + tokens.push([name, rule.style.getPropertyValue(name).trim()]); + } + } + } + } + + this.themeTokens.set(tokens); + } + + private seedFromStylesheet(): void { + const styles = getComputedStyle(this.stage().nativeElement); + + this.colors.update(current => ({ + ...current, + background: styles.getPropertyValue('--ig-grid-background').trim(), + accentColor: styles.getPropertyValue('--ig-grid-accent-color').trim() + })); + } +} diff --git a/src/app/theming/grid-theme-playground/theme-color-field.component.html b/src/app/theming/grid-theme-playground/theme-color-field.component.html new file mode 100644 index 0000000000..69afda312c --- /dev/null +++ b/src/app/theming/grid-theme-playground/theme-color-field.component.html @@ -0,0 +1,20 @@ +
+ {{ label() }} + + + + + + + + + @if (value()) { + + + + } + +
diff --git a/src/app/theming/grid-theme-playground/theme-color-field.component.scss b/src/app/theming/grid-theme-playground/theme-color-field.component.scss new file mode 100644 index 0000000000..f06f7a1d6a --- /dev/null +++ b/src/app/theming/grid-theme-playground/theme-color-field.component.scss @@ -0,0 +1,76 @@ +.color-field { + display: flex; + flex-direction: column; + gap: 6px; + + &__label { + font-size: 12px; + color: var(--ig-gray-700); + } + + igx-input-group { + --ig-input-group-focused-border-color: var(--ig-gray-500); + + [igxInput] { + font-size: 13px; + font-weight: 400; + } + } + + &__swatch-wrap { + position: relative; + width: 28px; + height: 28px; + flex-shrink: 0; + + input[type='color'] { + appearance: none; + position: absolute; + inset: 0; + opacity: 0; + width: 100%; + height: 100%; + cursor: pointer; + border: none; + padding: 0; + margin: 0; + } + } + + &__swatch { + position: absolute; + inset: 0; + border-radius: 5px; + border: 1px solid var(--ig-gray-400); + pointer-events: none; + } + + // "auto" -- the token is deriving rather than being overridden. + &__swatch--auto { + background: repeating-linear-gradient( + 45deg, + var(--ig-gray-200) 0 4px, + var(--ig-gray-100) 4px 8px + ); + } + + &__reset { + display: inline-flex; + align-items: center; + justify-content: center; + width: 24px; + height: 24px; + border: none; + border-radius: 4px; + background: transparent; + color: var(--ig-gray-600); + font-size: 14px; + cursor: pointer; + transition: color 120ms ease, background 120ms ease; + + &:hover { + color: var(--ig-gray-700); + background: var(--ig-gray-100); + } + } +} diff --git a/src/app/theming/grid-theme-playground/theme-color-field.component.ts b/src/app/theming/grid-theme-playground/theme-color-field.component.ts new file mode 100644 index 0000000000..47e532ae51 --- /dev/null +++ b/src/app/theming/grid-theme-playground/theme-color-field.component.ts @@ -0,0 +1,48 @@ +import { ChangeDetectionStrategy, Component, input, output } from '@angular/core'; +import { IgxInputGroupComponent, IgxInputDirective, IgxPrefixDirective, IgxSuffixDirective } from 'igniteui-angular/input-group'; +import { normalizeHexColor } from './color-utils'; + +@Component({ + selector: 'app-theme-color-field', + changeDetection: ChangeDetectionStrategy.OnPush, + templateUrl: './theme-color-field.component.html', + styleUrl: './theme-color-field.component.scss', + imports: [IgxInputGroupComponent, IgxInputDirective, IgxPrefixDirective, IgxSuffixDirective] +}) +export class ThemeColorFieldComponent { + public readonly label = input.required(); + public readonly value = input(''); + /** Seed for the native colour wheel while the value is empty ("auto"). */ + public readonly fallback = input('#808080'); + public readonly valueChange = output(); + + private pendingFrame: number | null = null; + private latestPickedValue: string | null = null; + protected onPick(event: Event): void { + this.latestPickedValue = (event.target as HTMLInputElement).value; + if (this.pendingFrame !== null) return; + + this.pendingFrame = requestAnimationFrame(() => { + this.pendingFrame = null; + if (this.latestPickedValue !== null) { + this.valueChange.emit(this.latestPickedValue); + this.latestPickedValue = null; + } + }); + } + + protected onTextChange(event: Event): void { + const inputEl = event.target as HTMLInputElement; + const normalized = normalizeHexColor(inputEl.value); + if (normalized) { + this.valueChange.emit(normalized); + inputEl.value = normalized; + } else { + inputEl.value = this.value(); + } + } + + protected reset(): void { + this.valueChange.emit(''); + } +} diff --git a/src/app/theming/theming-routes-data.ts b/src/app/theming/theming-routes-data.ts index a3a73cff12..91182ac677 100644 --- a/src/app/theming/theming-routes-data.ts +++ b/src/app/theming/theming-routes-data.ts @@ -12,5 +12,6 @@ export const themingRoutesData = { 'roundness': { displayName: 'Roundness', parentName: 'Themes' }, 'angular-sample': { displayName: 'Angular Material Theming', parentName: 'Themes' }, 'bootstrap-sample': { displayName: 'Bootstrap Theming', parentName: 'Themes' }, - 'card-sample-shadow': { displayName: 'card-sample-shadow', parentName: 'Shadows' } + 'card-sample-shadow': { displayName: 'card-sample-shadow', parentName: 'Shadows' }, + 'grid-theme-playground': { displayName: 'Grid Theme Playground', parentName: 'Themes' } }; diff --git a/src/app/theming/theming.routes.ts b/src/app/theming/theming.routes.ts index 2a0cd5e31b..e0c41f9e93 100644 --- a/src/app/theming/theming.routes.ts +++ b/src/app/theming/theming.routes.ts @@ -1,9 +1,8 @@ import { Routes } from '@angular/router'; import { AnimationsSampleComponent } from './animations/animations-sample-1/animations-sample-1.component'; import { AnimationsSample2Component } from './animations/animations-sample-2/animations-sample-2.component'; -import { AngularMaterialComponent } from './angular/angular-sample.component'; -import { BootstrapComponent } from './bootstrap/bootstrap-sample.component'; import { DefaultThemeSampleComponent } from './default-theme-sample/default-theme-sample.component'; +import { GridThemePlaygroundComponent } from './grid-theme-playground/grid-theme-playground.component'; import { DisplayDensityComponent } from './display-density/display-density.component'; import { RoundnessComponent } from './roundness/roundness-sample.component'; import { CardSampleShadowComponent} from './shadows/card-sample-shadow/card-sample-shadow'; @@ -55,12 +54,12 @@ export const ThemingRoutes: Routes = [ path: 'animations-sample-2' }, { - component: AngularMaterialComponent, + loadComponent: () => import('./angular/angular-sample.component').then(m => m.AngularMaterialComponent), data: themingRoutesData['angular-sample'], path: 'angular-sample' }, { - component: BootstrapComponent, + loadComponent: () => import('./bootstrap/bootstrap-sample.component').then(m => m.BootstrapComponent), data: themingRoutesData['bootstrap-sample'], path: 'bootstrap-sample' }, @@ -68,5 +67,10 @@ export const ThemingRoutes: Routes = [ component: TailwindSampleComponent, data: themingRoutesData['tailwind-sample'], path: 'tailwind-sample' + }, + { + component: GridThemePlaygroundComponent, + data: themingRoutesData['grid-theme-playground'], + path: 'grid-theme-playground' } ]; diff --git a/src/app/tree-grid/tree-grid-rowStyles-sample/tree-grid-rowStyle.component.ts b/src/app/tree-grid/tree-grid-rowStyles-sample/tree-grid-rowStyle.component.ts index b18c2bcc1a..324867face 100644 --- a/src/app/tree-grid/tree-grid-rowStyles-sample/tree-grid-rowStyle.component.ts +++ b/src/app/tree-grid/tree-grid-rowStyles-sample/tree-grid-rowStyle.component.ts @@ -25,7 +25,7 @@ export class TreeGridRowStylesComponent implements OnInit { row.data['Title'].includes('Senior') ? '#f8f9fa' : null; // eslint-disable-next-line @typescript-eslint/member-ordering - public rowStyles = { + public rowStyles: any = { background: this.background, 'border-left': (row: RowType) => row.data['Title'] === 'CEO' || row.data['Title'].includes('President') ? '2px solid' : null, diff --git a/src/app/tree-grid/tree-grid-style/layout.scss b/src/app/tree-grid/tree-grid-style/layout.scss index 0fda2fbb55..5c88726269 100644 --- a/src/app/tree-grid/tree-grid-style/layout.scss +++ b/src/app/tree-grid/tree-grid-style/layout.scss @@ -1,5 +1,9 @@ +:host { + display: block; +} + .grid__wrapper { - margin: 10px; + padding: 10px; } .cell__inner { diff --git a/src/app/tree-grid/tree-grid-style/tree-grid-style.component.html b/src/app/tree-grid/tree-grid-style/tree-grid-style.component.html index 9eeff6a075..751efd7181 100644 --- a/src/app/tree-grid/tree-grid-style/tree-grid-style.component.html +++ b/src/app/tree-grid/tree-grid-style/tree-grid-style.component.html @@ -1,6 +1,38 @@
- - +
+ Pick a theme + + + @for (theme of themes; track theme.class) { + + } + + +

+ Custom themes, not built-in: each is a grid-theme() with its + own background and accent. +

+
+ + +
@@ -8,8 +40,8 @@
- - - + + +
diff --git a/src/app/tree-grid/tree-grid-style/tree-grid-style.component.scss b/src/app/tree-grid/tree-grid-style/tree-grid-style.component.scss index 2c0d35a3be..9a14ff29e1 100644 --- a/src/app/tree-grid/tree-grid-style/tree-grid-style.component.scss +++ b/src/app/tree-grid/tree-grid-style/tree-grid-style.component.scss @@ -1,16 +1,141 @@ @use "layout.scss"; @use "igniteui-angular/theming" as *; -$background: #292826; -$foreground: #eeece1; -$accent: #ffcd0f; - -$custom-grid: grid-theme( - $background: $background, - $foreground: $foreground, - $accent-color: $accent, -); - -:host { - @include tokens($custom-grid); +$studio-bg: #faf4ed; +$studio-accent: #907aa9; +$ledger-bg: #eceff4; +$ledger-accent: #5e81ac; +$editorial-bg: #333c43; +$editorial-accent: #a7c080; +$midnight-bg: #282a36; +$midnight-accent: #bd93f9; + +.theme-studio { + --ig-size: var(--ig-size-large); + --ig-radius-factor: 0.6; + + @include tokens(grid-theme( + $background: $studio-bg, + $foreground: #575279, + $accent-color: $studio-accent, + $header-background: #fffaf3, + $header-border-color: #dfdad9, + $row-border-color: #f2e9e1, + $grid-border-color: #dfdad9, + $grid-shadow: (0 1px 3px rgba(87, 82, 121, 0.10), 0 1px 2px rgba(87, 82, 121, 0.06)) + )); +} + +.theme-ledger { + --ig-size: var(--ig-size-small); + --ig-radius-factor: 0; + + @include tokens(grid-theme( + $background: $ledger-bg, + $foreground: #2e3440, + $accent-color: $ledger-accent, + $header-background: #d8dee9, + $row-odd-background: #eceff4, + $row-even-background: #e5e9f0, + $body-column-border-color-odd: #d8dee9, + $body-column-border-color-even: #d8dee9, + $row-border-color: #d8dee9, + $grid-border-color: #c8d0dc + )); +} + +.theme-editorial { + --ig-size: var(--ig-size-large); + --ig-radius-factor: 0; + + @include tokens(grid-theme( + $schema: $dark-material-schema, + $background: $editorial-bg, + $foreground: #d3c6aa, + $accent-color: $editorial-accent, + $header-background: #3a464c, + $row-border-color: #333c43, + $grid-border-color: #333c43 + )); +} + +.theme-midnight { + --ig-size: var(--ig-size-medium); + --ig-radius-factor: 0.25; + + @include tokens(grid-theme( + $schema: $dark-material-schema, + $background: $midnight-bg, + $foreground: #f8f8f2, + $accent-color: $midnight-accent, + $header-background: #21222c, + $body-column-border-color-odd: #44475a, + $body-column-border-color-even: #44475a, + $row-border-color: #44475a, + $grid-border-color: #44475a + )); +} + +.grid__wrapper { + display: flex; + flex-direction: column; + gap: 16px; +} + +.theme-picker { + --ig-button-group-elevation: 0; + + display: flex; + flex-direction: column; + gap: 6px; + align-self: flex-start; +} + +.theme-picker__label { + font-size: 12px; + font-weight: 500; + letter-spacing: 0.04em; + color: var(--ig-gray-700); +} + +.theme-picker__hint { + margin: 0; + font-size: 12px; + line-height: 1.45; + color: var(--ig-gray-600); + + code { + font-family: 'JetBrains Mono', 'Fira Code', Consolas, monospace; + font-size: 11px; + } +} + +.theme-swatch { + display: inline-block; + min-width: 14px; + aspect-ratio: 1; + border-radius: 50%; + border: 1px solid var(--ig-gray-300); + vertical-align: -2px; + background: linear-gradient(135deg, var(--swatch-bg) 0 50%, var(--swatch-accent) 50% 100%); +} + +.theme-swatch--studio { + --swatch-bg: #{$studio-bg}; + --swatch-accent: #{$studio-accent}; +} + +.theme-swatch--ledger { + --swatch-bg: #{$ledger-bg}; + --swatch-accent: #{$ledger-accent}; +} + +.theme-swatch--editorial { + --swatch-bg: #{$editorial-bg}; + --swatch-accent: #{$editorial-accent}; +} + +.theme-swatch--midnight { + --swatch-bg: #{$midnight-bg}; + --swatch-accent: #{$midnight-accent}; } diff --git a/src/app/tree-grid/tree-grid-style/tree-grid-style.component.ts b/src/app/tree-grid/tree-grid-style/tree-grid-style.component.ts index 653f39c22b..f2a2472061 100644 --- a/src/app/tree-grid/tree-grid-style/tree-grid-style.component.ts +++ b/src/app/tree-grid/tree-grid-style/tree-grid-style.component.ts @@ -1,21 +1,35 @@ -import { Component, OnInit } from '@angular/core'; -import { EMPLOYEE_FLAT_AVATARS_DATA } from '../data/employees-flat-avatars'; -import { IgxTreeGridComponent } from 'igniteui-angular/grids/tree-grid'; -import { IgxCellTemplateDirective, IgxColumnComponent } from 'igniteui-angular/grids/core'; +import { Component, OnInit, signal } from '@angular/core'; import { IgxAvatarComponent } from 'igniteui-angular/avatar'; +import { IgxButtonGroupComponent } from 'igniteui-angular/button-group'; +import { IgxButtonDirective } from 'igniteui-angular/directives'; +import { IgxCellTemplateDirective, IgxColumnComponent } from 'igniteui-angular/grids/core'; +import { IgxTreeGridComponent } from 'igniteui-angular/grids/tree-grid'; +import { EMPLOYEE_FLAT_AVATARS_DATA } from '../data/employees-flat-avatars'; import { IgxPreventDocumentScrollDirective } from '../../directives/prevent-scroll.directive'; @Component({ selector: 'app-tree-grid-style', styleUrls: ['./tree-grid-style.component.scss'], templateUrl: './tree-grid-style.component.html', - imports: [IgxTreeGridComponent, IgxPreventDocumentScrollDirective, IgxColumnComponent, IgxCellTemplateDirective, IgxAvatarComponent] + imports: [IgxTreeGridComponent, IgxPreventDocumentScrollDirective, IgxColumnComponent, IgxCellTemplateDirective, IgxAvatarComponent, IgxButtonGroupComponent, IgxButtonDirective] }) export class TreeGridStyleComponent implements OnInit { public data: any[]; + public themes = [ + { label: 'Studio', class: 'theme-studio', swatch: 'theme-swatch--studio' }, + { label: 'Ledger', class: 'theme-ledger', swatch: 'theme-swatch--ledger' }, + { label: 'Editorial', class: 'theme-editorial', swatch: 'theme-swatch--editorial' }, + { label: 'Midnight', class: 'theme-midnight', swatch: 'theme-swatch--midnight' } + ]; + + public activeTheme = signal(this.themes[0].class); public ngOnInit() { this.data = EMPLOYEE_FLAT_AVATARS_DATA(); } + + public selectTheme(args: { index: number }) { + this.activeTheme.set(this.themes[args.index].class); + } } diff --git a/src/app/tree-grid/tree-grid-validation-style/tree-grid-validation-style.component.ts b/src/app/tree-grid/tree-grid-validation-style/tree-grid-validation-style.component.ts index dad7ada413..b6d515bcd4 100644 --- a/src/app/tree-grid/tree-grid-validation-style/tree-grid-validation-style.component.ts +++ b/src/app/tree-grid/tree-grid-validation-style/tree-grid-validation-style.component.ts @@ -17,7 +17,7 @@ export class TreeGridValidationStyleComponent implements OnInit { public data: IEmployee[]; public columns: any[]; public selectionMode = 'multiple'; - public rowStyles = { + public rowStyles: any = { background: (row: RowType) => row.cells.find(c => c.validation.errors !== null && c.validation.errors !== undefined) ? '#FF000033' : '#00000000' }; public cellStyles = { From dc183449b8c659fb7e3ca9aa564609f19c662d27 Mon Sep 17 00:00:00 2001 From: Simeon Simeonoff Date: Fri, 28 Aug 2026 17:18:29 +0300 Subject: [PATCH 2/3] lint: fix lint errors --- .../grid-theme-playground/grid-theme-playground.component.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/app/theming/grid-theme-playground/grid-theme-playground.component.ts b/src/app/theming/grid-theme-playground/grid-theme-playground.component.ts index f16a90f90d..1ed058dded 100644 --- a/src/app/theming/grid-theme-playground/grid-theme-playground.component.ts +++ b/src/app/theming/grid-theme-playground/grid-theme-playground.component.ts @@ -130,7 +130,7 @@ export class GridThemePlaygroundComponent { private readonly stage = viewChild.required>('stage'); /** The compiled theme, read from the stylesheet: the CSS export needs every * token, not just the roots, to work in an app with no grid-theme(). */ - private readonly themeTokens = signal>([]); + private readonly themeTokens = signal<[string, string][]>([]); constructor() { afterNextRender(() => { @@ -278,7 +278,7 @@ export class GridThemePlaygroundComponent { this.preview.set((['grid', 'tree', 'hierarchical', 'pivot'] as const)[args.index]); } private readCompiledTheme(): void { - const tokens: Array<[string, string]> = []; + const tokens: [string, string][] = []; for (const sheet of Array.from(document.styleSheets)) { let rules: CSSRuleList; From f7a74fc4820179bed7470d17382db2bf023abf0b Mon Sep 17 00:00:00 2001 From: Simeon Simeonoff Date: Fri, 28 Aug 2026 17:22:22 +0300 Subject: [PATCH 3/3] revert(theming): restore eager routes for Material and Bootstrap samples --- src/app/theming/theming.routes.ts | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/app/theming/theming.routes.ts b/src/app/theming/theming.routes.ts index e0c41f9e93..b1e0d0aac8 100644 --- a/src/app/theming/theming.routes.ts +++ b/src/app/theming/theming.routes.ts @@ -1,6 +1,8 @@ import { Routes } from '@angular/router'; import { AnimationsSampleComponent } from './animations/animations-sample-1/animations-sample-1.component'; import { AnimationsSample2Component } from './animations/animations-sample-2/animations-sample-2.component'; +import { AngularMaterialComponent } from './angular/angular-sample.component'; +import { BootstrapComponent } from './bootstrap/bootstrap-sample.component'; import { DefaultThemeSampleComponent } from './default-theme-sample/default-theme-sample.component'; import { GridThemePlaygroundComponent } from './grid-theme-playground/grid-theme-playground.component'; import { DisplayDensityComponent } from './display-density/display-density.component'; @@ -54,12 +56,12 @@ export const ThemingRoutes: Routes = [ path: 'animations-sample-2' }, { - loadComponent: () => import('./angular/angular-sample.component').then(m => m.AngularMaterialComponent), + component: AngularMaterialComponent, data: themingRoutesData['angular-sample'], path: 'angular-sample' }, { - loadComponent: () => import('./bootstrap/bootstrap-sample.component').then(m => m.BootstrapComponent), + component: BootstrapComponent, data: themingRoutesData['bootstrap-sample'], path: 'bootstrap-sample' },