diff --git a/projects/core/utils/dom/process-icon.ts b/projects/core/utils/dom/process-icon.ts index e88c3ded7b1a..dba464739925 100644 --- a/projects/core/utils/dom/process-icon.ts +++ b/projects/core/utils/dom/process-icon.ts @@ -2,29 +2,32 @@ const WIDTH_SEARCH = 'width="'; const HEIGHT_SEARCH = 'height="'; const START = '', indexOfStart)); +export function processIcon(source: string, name: string): string { + const src = source.substring(source.indexOf(START)); + const attributes = src.substring(0, src.indexOf('>')); if ( - !attibutes || - !attibutes.includes(WIDTH_SEARCH) || - !attibutes.includes(HEIGHT_SEARCH) + !attributes || + !attributes.includes(WIDTH_SEARCH) || + !attributes.includes(HEIGHT_SEARCH) ) { - return ''; + return src.replace( + START, + `'; } - const indexOfWidth = attibutes.indexOf(WIDTH_SEARCH); - const indexOfHeight = attibutes.indexOf(HEIGHT_SEARCH); + const indexOfWidth = attributes.indexOf(WIDTH_SEARCH); + const indexOfHeight = attributes.indexOf(HEIGHT_SEARCH); const widthOffset = indexOfWidth + WIDTH_SEARCH.length; const heightOffset = indexOfHeight + HEIGHT_SEARCH.length; - const widthString = attibutes.substring( + const widthString = attributes.substring( widthOffset, - attibutes.indexOf('"', widthOffset), + attributes.indexOf('"', widthOffset), ); - const heightString = attibutes.substring( + const heightString = attributes.substring( heightOffset, - attibutes.indexOf('"', heightOffset), + attributes.indexOf('"', heightOffset), ); if ( diff --git a/projects/demo/src/assets/icons/clear-24px.svg b/projects/demo/src/assets/icons/clear-24px.svg new file mode 100644 index 000000000000..84c0571123a7 --- /dev/null +++ b/projects/demo/src/assets/icons/clear-24px.svg @@ -0,0 +1 @@ + diff --git a/projects/demo/src/assets/icons/date_range-24px.svg b/projects/demo/src/assets/icons/date_range-24px.svg new file mode 100644 index 000000000000..732f27700360 --- /dev/null +++ b/projects/demo/src/assets/icons/date_range-24px.svg @@ -0,0 +1 @@ + diff --git a/projects/demo/src/assets/icons/help-24px.svg b/projects/demo/src/assets/icons/help-24px.svg new file mode 100644 index 000000000000..6ac33c08502b --- /dev/null +++ b/projects/demo/src/assets/icons/help-24px.svg @@ -0,0 +1 @@ + diff --git a/projects/demo/src/assets/icons/info-16px.svg b/projects/demo/src/assets/icons/info-16px.svg new file mode 100644 index 000000000000..bd22b4bde000 --- /dev/null +++ b/projects/demo/src/assets/icons/info-16px.svg @@ -0,0 +1 @@ + diff --git a/projects/demo/src/assets/icons/keyboard_arrow_left-24px.svg b/projects/demo/src/assets/icons/keyboard_arrow_left-24px.svg new file mode 100644 index 000000000000..b571a8124d29 --- /dev/null +++ b/projects/demo/src/assets/icons/keyboard_arrow_left-24px.svg @@ -0,0 +1 @@ + diff --git a/projects/demo/src/assets/icons/keyboard_arrow_right-24px.svg b/projects/demo/src/assets/icons/keyboard_arrow_right-24px.svg new file mode 100644 index 000000000000..e405e79b5abd --- /dev/null +++ b/projects/demo/src/assets/icons/keyboard_arrow_right-24px.svg @@ -0,0 +1 @@ + diff --git a/projects/icons/scripts/process-icons.js b/projects/demo/src/assets/process-icons.js similarity index 80% rename from projects/icons/scripts/process-icons.js rename to projects/demo/src/assets/process-icons.js index 92403b426013..56b620a322ec 100644 --- a/projects/icons/scripts/process-icons.js +++ b/projects/demo/src/assets/process-icons.js @@ -13,8 +13,11 @@ function processIcons() { const wrapped = wrapIcon(src, file.replace('.svg', '')); const final = typeof wrapped === 'string' - ? wrapped.replace(START, '${wrapped.src}`; + ? wrapped.replace( + START, + `' + : `${wrapped.src}`; fs.writeFileSync(file, final); }); @@ -22,9 +25,8 @@ function processIcons() { } function wrapIcon(source, name) { - const indexOfSTART = source.indexOf(START); - const src = source.substring(indexOfSTART); - const attributes = src.substring(indexOfSTART, src.indexOf('>', indexOfSTART)); + const src = source.substring(source.indexOf(START)); + const attributes = src.substring(0, src.indexOf('>')); if ( !attributes || diff --git a/projects/demo/src/modules/app/app.routes.ts b/projects/demo/src/modules/app/app.routes.ts index 9cfd72399586..72b3325be296 100644 --- a/projects/demo/src/modules/app/app.routes.ts +++ b/projects/demo/src/modules/app/app.routes.ts @@ -60,6 +60,16 @@ export const ROUTES = [ title: `Wrapper`, }, }, + { + path: 'icon-set', + loadChildren: () => + import(`../customization/icon-set/icon-set.module`).then( + m => m.IconSetModule, + ), + data: { + title: `Icon set`, + }, + }, { path: 'tui-doc', loadChildren: () => import(`../info/doc/doc.module`).then(m => m.DocModule), diff --git a/projects/demo/src/modules/app/pages.ts b/projects/demo/src/modules/app/pages.ts index 0c51d08cbbbb..b7aa4a43bfe0 100644 --- a/projects/demo/src/modules/app/pages.ts +++ b/projects/demo/src/modules/app/pages.ts @@ -125,6 +125,12 @@ export const pages: TuiDocPages = [ keywords: 'colors, css, theme, custom, style', route: 'wrapper', }, + { + section: $localize`Customization`, + title: `Icon set`, + keywords: 'icons, svg, theme, custom, style', + route: 'icon-set', + }, { section: $localize`Components`, title: 'Accordion', diff --git a/projects/demo/src/modules/customization/icon-set/examples/1/index.html b/projects/demo/src/modules/customization/icon-set/examples/1/index.html new file mode 100644 index 000000000000..39a4c5c68fd2 --- /dev/null +++ b/projects/demo/src/modules/customization/icon-set/examples/1/index.html @@ -0,0 +1,15 @@ +
+ + As usual with the DI this is hierarchical. Meaning you can provide + different TUI_ICONS_PATH and use different icons across + your app. + + + Pick your favorite date range + +
diff --git a/projects/demo/src/modules/customization/icon-set/examples/1/index.less b/projects/demo/src/modules/customization/icon-set/examples/1/index.less new file mode 100644 index 000000000000..b4a94d19fca1 --- /dev/null +++ b/projects/demo/src/modules/customization/icon-set/examples/1/index.less @@ -0,0 +1,117 @@ +tui-wrapper[data-appearance='material-textfield'] { + background: #f5f5f5; + color: rgba(0, 0, 0, 0.87); + border-radius: 4px 4px 0 0; + + &:after { + height: 1px; + background: #8e8e8e; + top: auto; + border: none; + transform-origin: bottom; + transition: all 0.3s; + } + + &[data-state='hovered'] { + background: #ececec; + + &:after { + background: #1f1f1f; + } + } + + &._focused { + background: #dcdcdc; + + // TODO: Better internal elements customization + label { + color: #6200ee !important; + } + + &:after { + background: #6200ee; + transform: scaleY(2); + } + } +} + +tui-wrapper[data-appearance='material-button'] { + border-radius: 4px; + background: #6200ee; + color: #fff; + text-transform: uppercase; + font-weight: bold; + box-shadow: 0 3px 1px -2px rgba(0, 0, 0, 0.2), 0px 2px 2px 0px rgba(0, 0, 0, 0.14%), + 0px 1px 5px 0px rgba(0, 0, 0, 0.12); + transition: all 0.3s; + + &[data-state='hovered'] { + background: #6e14ef; + box-shadow: 0 2px 4px -1px rgba(0, 0, 0, 0.2), 0px 4px 5px 0px rgba(0, 0, 0, 0.14), + 0px 1px 10px 0px rgba(0, 0, 0, 0.12); + } + + &[data-state='pressed'] { + background: #6e14ef; + box-shadow: 0 5px 5px -3px rgba(0, 0, 0, 0.2), 0px 8px 10px 1px rgba(0, 0, 0, 0.14), + 0px 3px 14px 2px rgba(0, 0, 0, 0.12); + } + + &._focused { + background: #883df2; + + &:after { + display: none; + } + } +} + +tui-wrapper[data-appearance='material-checkbox-on'], +tui-wrapper[data-appearance='material-checkbox-off'] { + color: #fff; + + &:before { + position: absolute; + content: ''; + top: 0; + left: 0; + right: 0; + bottom: 0; + border-radius: 2px; + border: 2px solid rgba(0, 0, 0, 0.54); + transition: all 0.3s; + } + + &:after { + position: absolute; + top: -8px; + left: -8px; + right: -8px; + bottom: -8px; + border-radius: 100%; + background: #000; + opacity: 0; + transition: opacity 0.3s; + } + + &[data-state='hovered'] { + &:after { + opacity: 0.05; + } + } + + &[data-state='pressed'], + &._focused { + &:after { + opacity: 0.1; + } + } +} + +tui-wrapper[data-appearance='material-checkbox-on'] { + &:before, + &:after { + background: #6200ee; + border-color: transparent; + } +} diff --git a/projects/demo/src/modules/customization/icon-set/examples/1/index.ts b/projects/demo/src/modules/customization/icon-set/examples/1/index.ts new file mode 100644 index 000000000000..808308186246 --- /dev/null +++ b/projects/demo/src/modules/customization/icon-set/examples/1/index.ts @@ -0,0 +1,32 @@ +import {Component} from '@angular/core'; +import {TUI_ICONS_PATH} from '@taiga-ui/core'; + +const MAPPER: Record = { + tuiIconCalendarLarge: 'date_range-24px', + tuiIconTooltipLarge: 'help-24px', + tuiIconInfo: 'info-16px', + tuiIconCloseLarge: 'clear-24px', + tuiIconChevronLeftLarge: 'keyboard_arrow_left-24px', + tuiIconChevronRightLarge: 'keyboard_arrow_right-24px', + // and so on +}; + +// This assumes that icons were properly processed +export function iconsPath(name: string): string { + return `assets/icons/${MAPPER[name]}.svg#${MAPPER[name]}`; +} + +@Component({ + selector: 'tui-icon-set-example-1', + templateUrl: './index.html', + styleUrls: ['./index.less'], + providers: [ + { + provide: TUI_ICONS_PATH, + useValue: iconsPath, + }, + ], +}) +export class TuiIconSetExample1 { + date = null; +} diff --git a/projects/demo/src/modules/customization/icon-set/icon-set.component.ts b/projects/demo/src/modules/customization/icon-set/icon-set.component.ts new file mode 100644 index 000000000000..9175e971fc81 --- /dev/null +++ b/projects/demo/src/modules/customization/icon-set/icon-set.component.ts @@ -0,0 +1,30 @@ +import {default as example1Html} from '!!raw-loader!./examples/1/index.html'; +import {default as example1Less} from '!!raw-loader!./examples/1/index.less'; +import {default as example1Ts} from '!!raw-loader!./examples/1/index.ts'; + +import {Component} from '@angular/core'; +import {tuiCoreIcons, tuiKitIcons} from '@taiga-ui/icons'; +import {changeDetection} from '../../../change-detection-strategy'; +import {FrontEndExample} from '../../interfaces/front-end-example'; + +@Component({ + selector: 'icon-set', + templateUrl: './icon-set.template.html', + styleUrls: ['./icon-set.style.less'], + changeDetection, +}) +export class IconSetComponent { + readonly example1: FrontEndExample = { + TypeScript: example1Ts, + HTML: example1Html, + LESS: example1Less, + }; + + readonly names = [...Object.keys(tuiCoreIcons), ...Object.keys(tuiKitIcons)]; + + expanded = false; + + toggle() { + this.expanded = !this.expanded; + } +} diff --git a/projects/demo/src/modules/customization/icon-set/icon-set.module.ts b/projects/demo/src/modules/customization/icon-set/icon-set.module.ts new file mode 100644 index 000000000000..e6bc3f5d288c --- /dev/null +++ b/projects/demo/src/modules/customization/icon-set/icon-set.module.ts @@ -0,0 +1,42 @@ +import {ClipboardModule} from '@angular/cdk/clipboard'; +import {CommonModule} from '@angular/common'; +import {NgModule} from '@angular/core'; +import {FormsModule} from '@angular/forms'; +import {RouterModule} from '@angular/router'; +import { + generateRoutes, + TUI_DOC_PAGE_MODULES, + TuiDocCopyModule, +} from '@taiga-ui/addon-doc'; +import { + TuiButtonModule, + TuiExpandModule, + TuiHintControllerModule, + TuiLinkModule, + TuiNotificationModule, + TuiTextfieldControllerModule, +} from '@taiga-ui/core'; +import {TuiInputDateRangeModule} from '@taiga-ui/kit'; +import {TuiIconSetExample1} from './examples/1'; +import {IconSetComponent} from './icon-set.component'; + +@NgModule({ + imports: [ + CommonModule, + FormsModule, + ClipboardModule, + TuiDocCopyModule, + TuiLinkModule, + TuiExpandModule, + TuiButtonModule, + TuiInputDateRangeModule, + TuiNotificationModule, + TuiTextfieldControllerModule, + TuiHintControllerModule, + ...TUI_DOC_PAGE_MODULES, + RouterModule.forChild(generateRoutes(IconSetComponent)), + ], + declarations: [IconSetComponent, TuiIconSetExample1], + exports: [IconSetComponent], +}) +export class IconSetModule {} diff --git a/projects/demo/src/modules/customization/icon-set/icon-set.style.less b/projects/demo/src/modules/customization/icon-set/icon-set.style.less new file mode 100644 index 000000000000..69ed3fc5d63a --- /dev/null +++ b/projects/demo/src/modules/customization/icon-set/icon-set.style.less @@ -0,0 +1,5 @@ +.wrapper { + display: flex; + flex-direction: column; + align-items: flex-start; +} diff --git a/projects/demo/src/modules/customization/icon-set/icon-set.template.html b/projects/demo/src/modules/customization/icon-set/icon-set.template.html new file mode 100644 index 000000000000..aa4f7742809e --- /dev/null +++ b/projects/demo/src/modules/customization/icon-set/icon-set.template.html @@ -0,0 +1,60 @@ + +

+ Taiga UI uses + tui-svg component to + display SVG icons. It can show icons by name, link or + source code. Several icon names are hardcoded into kit components so if + you want to switch to a different icon set you would need to provide a + mapping. +

+

+ +

+ +
+ + {{name}} + +
+
+

+ These are keys of following dictionaries: tuiCoreIcons and + tuiKitIcons. It is not required to provide all those icons, + you can gradually add the ones you need depending on components you use. +

+ +

+ There are two ways to add icons: bundled and as assets. Bundled icons + have the benefit of immediate display but increase your app size. Assets + work like regular + img src="xxx" with all the benefits of caching. In both + cases you can control color with CSS color style. +

+ + + Make sure you mark regions in your icons that need to be colored with + fill="currentColor" or stroke="currentColor". + + +

+ If you want to bundle your icons, you need to provide + TUI_ICONS token with a dictionary of name to source code. + If you want to move your icons to assets you need to process them with + this script. Drop it to the folder + with your icons and run node process-icons.js. Then move + your icons to the assets folder and provide + TUI_ICONS_PATH as seen in example below: +

+ + + + +
diff --git a/projects/demo/src/modules/customization/variables/variables.component.ts b/projects/demo/src/modules/customization/variables/variables.component.ts index 69457aeaff2f..39dc32af717e 100644 --- a/projects/demo/src/modules/customization/variables/variables.component.ts +++ b/projects/demo/src/modules/customization/variables/variables.component.ts @@ -1,15 +1,13 @@ import {default as example1Html} from '!!raw-loader!./examples/1/index.html'; import {default as example1Less} from '!!raw-loader!./examples/1/index.less'; -import {Component, ViewEncapsulation} from '@angular/core'; +import {Component} from '@angular/core'; import {changeDetection} from '../../../change-detection-strategy'; import {FrontEndExample} from '../../interfaces/front-end-example'; @Component({ selector: 'variables', templateUrl: './variables.template.html', - styleUrls: ['./variables.style.less'], - encapsulation: ViewEncapsulation.None, changeDetection, }) export class VariablesComponent { diff --git a/projects/demo/src/modules/customization/variables/variables.style.less b/projects/demo/src/modules/customization/variables/variables.style.less deleted file mode 100644 index e69de29bb2d1..000000000000