├── .browserslistrc ├── .editorconfig ├── .gitignore ├── .husky └── commit-msg ├── .prettierrc ├── LICENSE ├── LICENSE.md ├── README.md ├── angular.json ├── commitlint.config.js ├── docs └── assets │ ├── detail-page.jpg │ └── feature-image.jpg ├── karma.conf.js ├── package-lock.json ├── package.json ├── src ├── app │ ├── app-routing.module.ts │ ├── app.component.ts │ ├── app.module.ts │ ├── components │ │ ├── code-explorer │ │ │ ├── code-explorer.component.html │ │ │ ├── code-explorer.component.ts │ │ │ └── code-explorer.module.ts │ │ ├── footer │ │ │ ├── footer.component.spec.ts │ │ │ └── footer.component.ts │ │ ├── header │ │ │ ├── header.component.spec.ts │ │ │ ├── header.component.ts │ │ │ └── header.module.ts │ │ ├── page-header │ │ │ ├── page-header.component.ts │ │ │ └── page-header.module.ts │ │ ├── section-card │ │ │ ├── section-card.component.spec.ts │ │ │ ├── section-card.component.ts │ │ │ └── section-card.module.ts │ │ └── stats-card │ │ │ ├── stats-card.component.ts │ │ │ └── stats-card.module.ts │ ├── config │ │ ├── badges-code.config.ts │ │ ├── fullscreen-code.config..ts │ │ ├── highlight-code.config.ts │ │ ├── long-press-code.config.ts │ │ ├── permissions-code.config.ts │ │ ├── permissions.config.ts │ │ ├── sections.config.ts │ │ ├── stats-code.config.ts │ │ └── table-sort-code.config.ts │ ├── interfaces │ │ └── user.interface.ts │ ├── lib │ │ ├── ui-badge │ │ │ ├── badge.interface.ts │ │ │ ├── badge.styles.scss │ │ │ ├── ui-badge.directive.ts │ │ │ └── ui-badge.module.ts │ │ ├── ui-buttons │ │ │ └── ui-buttons.module.ts │ │ ├── ui-fullscreen │ │ │ ├── ui-fullscreen.directive.ts │ │ │ └── ui-fullscreen.module.ts │ │ ├── ui-highlight │ │ │ ├── ui-highlight.directive.ts │ │ │ └── ui-highlight.module.ts │ │ ├── ui-long-press │ │ │ ├── ui-long-press.directive.ts │ │ │ └── ui-long-press.module.ts │ │ ├── ui-permissions │ │ │ ├── ui-permissions.directive.ts │ │ │ └── ui-permissions.module.ts │ │ ├── ui-stats-card │ │ │ ├── stats-delta-color-arrow.directive.ts │ │ │ ├── stats-delta-color.directive.ts │ │ │ └── ui-stats-card.module.ts │ │ └── ui-table-sort │ │ │ ├── ui-table-sort.directive.ts │ │ │ └── ui-table-sort.module.ts │ ├── pages │ │ ├── badges │ │ │ ├── badges-routing.module.ts │ │ │ ├── badges.component.ts │ │ │ └── badges.module.ts │ │ ├── fullscreen │ │ │ ├── fullscreen-routing.module.ts │ │ │ ├── fullscreen.component.ts │ │ │ └── fullscreen.module.ts │ │ ├── highlight │ │ │ ├── highlight-routing.module.ts │ │ │ ├── highlight.component.ts │ │ │ └── highlight.module.ts │ │ ├── home │ │ │ └── home.component.ts │ │ ├── long-press │ │ │ ├── long-press-routing.module.ts │ │ │ ├── long-press.component.ts │ │ │ └── long-press.module.ts │ │ ├── permissions │ │ │ ├── permissions-routing.module.ts │ │ │ ├── permissions.component.ts │ │ │ └── permissions.module.ts │ │ ├── stats │ │ │ ├── stats-routing.module.ts │ │ │ ├── stats.component.ts │ │ │ └── stats.module.ts │ │ └── table-sort │ │ │ ├── table-sort-routing.module.ts │ │ │ ├── table-sort.component.ts │ │ │ └── table-sort.module.ts │ └── services │ │ ├── auth.service.spec.ts │ │ └── auth.service.ts ├── assets │ ├── .gitkeep │ ├── icons │ │ ├── maximize.svg │ │ └── minimize.svg │ ├── images │ │ ├── angular-merch.jpg │ │ ├── badges.jpg │ │ ├── fullscreen.jpg │ │ ├── highlight.jpg │ │ ├── long-press.jpg │ │ ├── permissions.jpg │ │ ├── stats-card.jpg │ │ └── table-sort.jpg │ └── meta.jpg ├── environments │ ├── environment.prod.ts │ └── environment.ts ├── favicon.ico ├── index.html ├── main.ts ├── polyfills.ts ├── styles.scss └── test.ts ├── tailwind.config.js ├── tsconfig.app.json ├── tsconfig.json └── tsconfig.spec.json /.browserslistrc: -------------------------------------------------------------------------------- 1 | # This file is used by the build system to adjust CSS and JS output to support the specified browsers below. 2 | # For additional information regarding the format and rule options, please see: 3 | # https://github.com/browserslist/browserslist#queries 4 | 5 | # For the full list of supported browsers by the Angular framework, please see: 6 | # https://angular.io/guide/browser-support 7 | 8 | # You can see what browsers were selected by your queries by running: 9 | # npx browserslist 10 | 11 | last 1 Chrome version 12 | last 1 Firefox version 13 | last 2 Edge major versions 14 | last 2 Safari major versions 15 | last 2 iOS major versions 16 | Firefox ESR 17 | not IE 11 # Angular supports IE 11 only as an opt-in. To opt-in, remove the 'not' prefix on this line. 18 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # Editor configuration, see https://editorconfig.org 2 | root = true 3 | 4 | [*] 5 | charset = utf-8 6 | indent_style = space 7 | indent_size = 2 8 | insert_final_newline = true 9 | trim_trailing_whitespace = true 10 | 11 | [*.ts] 12 | quote_type = single 13 | 14 | [*.md] 15 | max_line_length = off 16 | trim_trailing_whitespace = false 17 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See http://help.github.com/ignore-files/ for more about ignoring files. 2 | 3 | # compiled output 4 | /dist 5 | /tmp 6 | /out-tsc 7 | # Only exists if Bazel was run 8 | /bazel-out 9 | 10 | # dependencies 11 | /node_modules 12 | 13 | # profiling files 14 | chrome-profiler-events*.json 15 | 16 | # IDEs and editors 17 | /.idea 18 | .project 19 | .classpath 20 | .c9/ 21 | *.launch 22 | .settings/ 23 | *.sublime-workspace 24 | 25 | # IDE - VSCode 26 | .vscode/* 27 | !.vscode/settings.json 28 | !.vscode/tasks.json 29 | !.vscode/launch.json 30 | !.vscode/extensions.json 31 | .history/* 32 | 33 | # misc 34 | /.sass-cache 35 | /connect.lock 36 | /coverage 37 | /libpeerconnection.log 38 | npm-debug.log 39 | yarn-error.log 40 | testem.log 41 | /typings 42 | 43 | # System Files 44 | .DS_Store 45 | Thumbs.db 46 | -------------------------------------------------------------------------------- /.husky/commit-msg: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | . "$(dirname "$0")/_/husky.sh" 3 | 4 | npx --no-install commitlint --edit "$1" 5 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "singleQuote": true, 3 | "printWidth": 120, 4 | "trailingComma": "all", 5 | "semi": true, 6 | "endOfLine": "lf" 7 | } -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 Adithya Sreyaj 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/adisreyaj/angular-directives-showcase/fe227adb724569601f8d697ec2c0982841739384/LICENSE.md -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

Angular Directive Showcase

2 |

3 | angular 4 | Commitizen 5 | Prettier 6 |

7 | 8 | A collection of directives that can be used in different scenarios. See how to extract 9 | away the extra logics from components to have a more maintainable and reusable code. 10 | 11 | ![Angular Directives Showcase](./docs/assets/feature-image.jpg) 12 | 13 | ## Examples Showcased 14 | 15 | 1. Delta Value Arrow 16 | 1. Full-screen Toggle 17 | 1. Permission 18 | 1. Highlight Text 19 | 1. Long Press Directive 20 | 1. Badge Directive 21 | 1. Table Sort Directive 22 | 23 | Each directive has a dedicated page where the code is also displayed. 24 | 25 | ![Angular Directives Detail](./docs/assets/detail-page.jpg) 26 | 27 | --- 28 | 29 | ## Run Locally 30 | 31 | #### 1. Download or Clone the repository 32 | 33 | ```sh 34 | git clone https://github.com/adisreyaj/angular-directives-showcase.git 35 | ``` 36 | 37 | #### 2. Install dependencies 38 | 39 | ```sh 40 | npm install 41 | ``` 42 | 43 | #### 3. Run application 44 | 45 | ```sh 46 | npm start 47 | ``` 48 | 49 | #### 4. Open the URL in browser 50 | 51 | ``` 52 | http://localhost:4200 53 | ``` 54 | 55 | --- 56 | 57 | ## Support 58 | 59 | Don't forget to ⭐ the repository if you like it. 60 | 61 | 62 | Twitter 63 | 64 |
65 | 66 | GitHub 67 | 68 |
69 | 70 | LinkedIn 71 | 72 |
73 | 74 | BuyMeACoffee 75 | 76 | -------------------------------------------------------------------------------- /angular.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "./node_modules/@angular/cli/lib/config/schema.json", 3 | "version": 1, 4 | "newProjectRoot": "projects", 5 | "projects": { 6 | "ng-directives-showcase": { 7 | "projectType": "application", 8 | "schematics": { 9 | "@schematics/angular:component": { 10 | "style": "scss" 11 | }, 12 | "@schematics/angular:application": { 13 | "strict": true 14 | } 15 | }, 16 | "root": "", 17 | "sourceRoot": "src", 18 | "prefix": "app", 19 | "architect": { 20 | "build": { 21 | "builder": "@angular-devkit/build-angular:browser", 22 | "options": { 23 | "outputPath": "dist/ng-directives-showcase", 24 | "index": "src/index.html", 25 | "main": "src/main.ts", 26 | "polyfills": "src/polyfills.ts", 27 | "tsConfig": "tsconfig.app.json", 28 | "inlineStyleLanguage": "scss", 29 | "assets": [ 30 | "src/favicon.ico", 31 | "src/assets" 32 | ], 33 | "styles": [ 34 | "src/styles.scss" 35 | ], 36 | "scripts": [] 37 | }, 38 | "configurations": { 39 | "production": { 40 | "budgets": [ 41 | { 42 | "type": "initial", 43 | "maximumWarning": "500kb", 44 | "maximumError": "1mb" 45 | }, 46 | { 47 | "type": "anyComponentStyle", 48 | "maximumWarning": "2kb", 49 | "maximumError": "4kb" 50 | } 51 | ], 52 | "fileReplacements": [ 53 | { 54 | "replace": "src/environments/environment.ts", 55 | "with": "src/environments/environment.prod.ts" 56 | } 57 | ], 58 | "outputHashing": "all" 59 | }, 60 | "development": { 61 | "buildOptimizer": false, 62 | "optimization": false, 63 | "vendorChunk": true, 64 | "extractLicenses": false, 65 | "sourceMap": true, 66 | "namedChunks": true 67 | } 68 | }, 69 | "defaultConfiguration": "production" 70 | }, 71 | "serve": { 72 | "builder": "@angular-devkit/build-angular:dev-server", 73 | "configurations": { 74 | "production": { 75 | "browserTarget": "ng-directives-showcase:build:production" 76 | }, 77 | "development": { 78 | "browserTarget": "ng-directives-showcase:build:development" 79 | } 80 | }, 81 | "defaultConfiguration": "development" 82 | }, 83 | "extract-i18n": { 84 | "builder": "@angular-devkit/build-angular:extract-i18n", 85 | "options": { 86 | "browserTarget": "ng-directives-showcase:build" 87 | } 88 | }, 89 | "test": { 90 | "builder": "@angular-devkit/build-angular:karma", 91 | "options": { 92 | "main": "src/test.ts", 93 | "polyfills": "src/polyfills.ts", 94 | "tsConfig": "tsconfig.spec.json", 95 | "karmaConfig": "karma.conf.js", 96 | "inlineStyleLanguage": "scss", 97 | "assets": [ 98 | "src/favicon.ico", 99 | "src/assets" 100 | ], 101 | "styles": [ 102 | "src/styles.scss" 103 | ], 104 | "scripts": [] 105 | } 106 | } 107 | } 108 | } 109 | }, 110 | "defaultProject": "ng-directives-showcase" 111 | } 112 | -------------------------------------------------------------------------------- /commitlint.config.js: -------------------------------------------------------------------------------- 1 | module.exports = {extends: ['@commitlint/config-conventional']} 2 | -------------------------------------------------------------------------------- /docs/assets/detail-page.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/adisreyaj/angular-directives-showcase/fe227adb724569601f8d697ec2c0982841739384/docs/assets/detail-page.jpg -------------------------------------------------------------------------------- /docs/assets/feature-image.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/adisreyaj/angular-directives-showcase/fe227adb724569601f8d697ec2c0982841739384/docs/assets/feature-image.jpg -------------------------------------------------------------------------------- /karma.conf.js: -------------------------------------------------------------------------------- 1 | // Karma configuration file, see link for more information 2 | // https://karma-runner.github.io/1.0/config/configuration-file.html 3 | 4 | module.exports = function (config) { 5 | config.set({ 6 | basePath: '', 7 | frameworks: ['jasmine', '@angular-devkit/build-angular'], 8 | plugins: [ 9 | require('karma-jasmine'), 10 | require('karma-chrome-launcher'), 11 | require('karma-jasmine-html-reporter'), 12 | require('karma-coverage'), 13 | require('@angular-devkit/build-angular/plugins/karma') 14 | ], 15 | client: { 16 | jasmine: { 17 | // you can add configuration options for Jasmine here 18 | // the possible options are listed at https://jasmine.github.io/api/edge/Configuration.html 19 | // for example, you can disable the random execution with `random: false` 20 | // or set a specific seed with `seed: 4321` 21 | }, 22 | clearContext: false // leave Jasmine Spec Runner output visible in browser 23 | }, 24 | jasmineHtmlReporter: { 25 | suppressAll: true // removes the duplicated traces 26 | }, 27 | coverageReporter: { 28 | dir: require('path').join(__dirname, './coverage/ng-directives-showcase'), 29 | subdir: '.', 30 | reporters: [ 31 | { type: 'html' }, 32 | { type: 'text-summary' } 33 | ] 34 | }, 35 | reporters: ['progress', 'kjhtml'], 36 | port: 9876, 37 | colors: true, 38 | logLevel: config.LOG_INFO, 39 | autoWatch: true, 40 | browsers: ['Chrome'], 41 | singleRun: false, 42 | restartOnFileChange: true 43 | }); 44 | }; 45 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ng-directives-showcase", 3 | "version": "0.0.0", 4 | "scripts": { 5 | "ng": "ng", 6 | "start": "ng serve", 7 | "build": "ng build", 8 | "watch": "ng build --watch --configuration development", 9 | "test": "ng test" 10 | }, 11 | "private": true, 12 | "dependencies": { 13 | "@angular/animations": "~12.1.2", 14 | "@angular/common": "~12.1.2", 15 | "@angular/compiler": "~12.1.2", 16 | "@angular/core": "~12.1.2", 17 | "@angular/forms": "~12.1.2", 18 | "@angular/platform-browser": "~12.1.2", 19 | "@angular/platform-browser-dynamic": "~12.1.2", 20 | "@angular/router": "~12.1.2", 21 | "@tailwindcss/forms": "^0.3.3", 22 | "@tailwindcss/line-clamp": "^0.2.1", 23 | "ngx-highlight-js": "^12.0.0", 24 | "rxjs": "~6.6.0", 25 | "screenfull": "^5.1.0", 26 | "tslib": "^2.1.0", 27 | "zone.js": "~0.11.4" 28 | }, 29 | "devDependencies": { 30 | "@angular-devkit/build-angular": "~12.1.2", 31 | "@angular/cli": "~12.1.2", 32 | "@angular/compiler-cli": "~12.1.2", 33 | "@commitlint/cli": "^12.1.4", 34 | "@commitlint/config-conventional": "^12.1.4", 35 | "@types/jasmine": "~3.6.0", 36 | "@types/node": "^12.11.1", 37 | "husky": "^7.0.1", 38 | "jasmine-core": "~3.7.0", 39 | "karma": "~6.3.0", 40 | "karma-chrome-launcher": "~3.1.0", 41 | "karma-coverage": "~2.0.3", 42 | "karma-jasmine": "~4.0.0", 43 | "karma-jasmine-html-reporter": "^1.5.0", 44 | "tailwindcss": "^2.1.4", 45 | "typescript": "~4.2.3" 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/app/app-routing.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { RouterModule, Routes } from '@angular/router'; 3 | import { HomeComponent } from './pages/home/home.component'; 4 | 5 | const routes: Routes = [ 6 | { 7 | path: '', 8 | pathMatch: 'full', 9 | component: HomeComponent, 10 | }, 11 | { 12 | path: 'fullscreen', 13 | loadChildren: () => import('./pages/fullscreen/fullscreen.module').then((m) => m.FullscreenModule), 14 | }, 15 | { 16 | path: 'permissions', 17 | loadChildren: () => import('./pages/permissions/permissions.module').then((m) => m.PermissionsModule), 18 | }, 19 | { path: 'stats', loadChildren: () => import('./pages/stats/stats.module').then((m) => m.StatsModule) }, 20 | { path: 'highlight', loadChildren: () => import('./pages/highlight/highlight.module').then(m => m.HighlightModule) }, 21 | { path: 'long-press', loadChildren: () => import('./pages/long-press/long-press.module').then(m => m.LongPressModule) }, 22 | { path: 'badges', loadChildren: () => import('./pages/badges/badges.module').then(m => m.BadgesModule) }, 23 | { path: 'table-sort', loadChildren: () => import('./pages/table-sort/table-sort.module').then(m => m.TableSortModule) }, 24 | ]; 25 | 26 | @NgModule({ 27 | imports: [RouterModule.forRoot(routes)], 28 | exports: [RouterModule], 29 | }) 30 | export class AppRoutingModule { } 31 | -------------------------------------------------------------------------------- /src/app/app.component.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | 3 | @Component({ 4 | selector: 'app-root', 5 | template: ` 6 | 7 |
8 | 9 |
10 | 11 | `, 12 | styles: [ 13 | ` 14 | main { 15 | min-height: calc(100vh - 128px); 16 | } 17 | `, 18 | ], 19 | }) 20 | export class AppComponent {} 21 | -------------------------------------------------------------------------------- /src/app/app.module.ts: -------------------------------------------------------------------------------- 1 | import { DEFAULT_CURRENCY_CODE, NgModule } from '@angular/core'; 2 | import { BrowserModule } from '@angular/platform-browser'; 3 | import { AppRoutingModule } from './app-routing.module'; 4 | import { AppComponent } from './app.component'; 5 | import { HeaderModule } from './components/header/header.module'; 6 | import { SectionCardsModule } from './components/section-card/section-card.module'; 7 | import { HomeComponent } from './pages/home/home.component'; 8 | import { FooterComponent } from './components/footer/footer.component'; 9 | 10 | @NgModule({ 11 | declarations: [AppComponent, HomeComponent, FooterComponent], 12 | imports: [BrowserModule, AppRoutingModule, HeaderModule, SectionCardsModule], 13 | providers: [{ provide: DEFAULT_CURRENCY_CODE, useValue: 'USD' }], 14 | bootstrap: [AppComponent], 15 | }) 16 | export class AppModule {} 17 | -------------------------------------------------------------------------------- /src/app/components/code-explorer/code-explorer.component.html: -------------------------------------------------------------------------------- 1 |
2 | 3 | 5 | 6 |
7 | 8 | 9 | -------------------------------------------------------------------------------- /src/app/components/code-explorer/code-explorer.component.ts: -------------------------------------------------------------------------------- 1 | import { ChangeDetectionStrategy, Component, Input, OnDestroy, OnInit } from '@angular/core'; 2 | import { BehaviorSubject, Subject } from 'rxjs'; 3 | import { takeUntil } from 'rxjs/operators'; 4 | 5 | export interface CodeExplorerData { 6 | name: string; 7 | language: string; 8 | content: string; 9 | } 10 | 11 | @Component({ 12 | selector: 'app-code-explorer', 13 | templateUrl: './code-explorer.component.html', 14 | styles: [ 15 | ` 16 | .selector { 17 | @apply px-4 py-2 bg-gray-300 ring-inset rounded-tl-md rounded-tr-md; 18 | @apply focus:outline-none focus:ring-2 focus:ring-blue-600; 19 | @apply focus:ring-offset-gray-300 focus:ring-offset-2; 20 | } 21 | .active { 22 | @apply bg-blue-600; 23 | @apply focus:ring-white; 24 | @apply focus:ring-offset-blue-600; 25 | color: #fff; 26 | } 27 | `, 28 | ], 29 | changeDetection: ChangeDetectionStrategy.OnPush, 30 | }) 31 | export class CodeExplorerComponent implements OnInit, OnDestroy { 32 | @Input() 33 | set codes(data: CodeExplorerData[]) { 34 | const selectedTab = this.selectedTab$.getValue(); 35 | this.selectedCode$.next(data[selectedTab]); 36 | this._codes = data; 37 | } 38 | get codes() { 39 | return this._codes; 40 | } 41 | 42 | selectedTab$ = new BehaviorSubject(0); 43 | selectedCode$ = new BehaviorSubject(null); 44 | 45 | private _codes: CodeExplorerData[] = []; 46 | private destroy$ = new Subject(); 47 | constructor() {} 48 | 49 | ngOnInit(): void { 50 | this.selectedTab$.pipe(takeUntil(this.destroy$)).subscribe((index) => { 51 | this.selectedCode$.next(this.codes[index]); 52 | }); 53 | } 54 | 55 | ngOnDestroy() { 56 | this.destroy$.next(); 57 | this.destroy$.complete(); 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /src/app/components/code-explorer/code-explorer.module.ts: -------------------------------------------------------------------------------- 1 | import { CommonModule } from '@angular/common'; 2 | import { NgModule } from '@angular/core'; 3 | import { HighlightJsModule } from 'ngx-highlight-js'; 4 | import { CodeExplorerComponent } from './code-explorer.component'; 5 | 6 | @NgModule({ 7 | declarations: [CodeExplorerComponent], 8 | imports: [CommonModule, HighlightJsModule], 9 | exports: [CodeExplorerComponent], 10 | }) 11 | export class CodeExplorerModule {} 12 | -------------------------------------------------------------------------------- /src/app/components/footer/footer.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { ComponentFixture, TestBed } from '@angular/core/testing'; 2 | 3 | import { FooterComponent } from './footer.component'; 4 | 5 | describe('FooterComponent', () => { 6 | let component: FooterComponent; 7 | let fixture: ComponentFixture; 8 | 9 | beforeEach(async () => { 10 | await TestBed.configureTestingModule({ 11 | declarations: [ FooterComponent ] 12 | }) 13 | .compileComponents(); 14 | }); 15 | 16 | beforeEach(() => { 17 | fixture = TestBed.createComponent(FooterComponent); 18 | component = fixture.componentInstance; 19 | fixture.detectChanges(); 20 | }); 21 | 22 | it('should create', () => { 23 | expect(component).toBeTruthy(); 24 | }); 25 | }); 26 | -------------------------------------------------------------------------------- /src/app/components/footer/footer.component.ts: -------------------------------------------------------------------------------- 1 | import { ChangeDetectionStrategy, Component } from '@angular/core'; 2 | 3 | @Component({ 4 | selector: 'app-footer', 5 | template: ``, 56 | styles: [ 57 | ` 58 | :host { 59 | width: 100%; 60 | } 61 | `, 62 | ], 63 | changeDetection: ChangeDetectionStrategy.OnPush, 64 | }) 65 | export class FooterComponent {} 66 | -------------------------------------------------------------------------------- /src/app/components/header/header.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { ComponentFixture, TestBed } from '@angular/core/testing'; 2 | 3 | import { HeaderComponent } from './header.component'; 4 | 5 | describe('HeaderComponent', () => { 6 | let component: HeaderComponent; 7 | let fixture: ComponentFixture; 8 | 9 | beforeEach(async () => { 10 | await TestBed.configureTestingModule({ 11 | declarations: [ HeaderComponent ] 12 | }) 13 | .compileComponents(); 14 | }); 15 | 16 | beforeEach(() => { 17 | fixture = TestBed.createComponent(HeaderComponent); 18 | component = fixture.componentInstance; 19 | fixture.detectChanges(); 20 | }); 21 | 22 | it('should create', () => { 23 | expect(component).toBeTruthy(); 24 | }); 25 | }); 26 | -------------------------------------------------------------------------------- /src/app/components/header/header.component.ts: -------------------------------------------------------------------------------- 1 | import { ChangeDetectionStrategy, Component, OnInit } from '@angular/core'; 2 | 3 | @Component({ 4 | selector: 'app-header', 5 | template: ` 6 |
7 |
8 | Angular Directives 9 |
10 |
11 | 12 | 19 | 20 | 23 | 26 | 27 | 28 | 29 |
30 |
31 | `, 32 | styles: [], 33 | changeDetection: ChangeDetectionStrategy.OnPush, 34 | }) 35 | export class HeaderComponent implements OnInit { 36 | constructor() {} 37 | 38 | ngOnInit(): void {} 39 | } 40 | -------------------------------------------------------------------------------- /src/app/components/header/header.module.ts: -------------------------------------------------------------------------------- 1 | import { CommonModule } from '@angular/common'; 2 | import { NgModule } from '@angular/core'; 3 | import { RouterModule } from '@angular/router'; 4 | import { HeaderComponent } from './header.component'; 5 | 6 | @NgModule({ 7 | declarations: [HeaderComponent], 8 | imports: [CommonModule, RouterModule], 9 | exports: [HeaderComponent], 10 | }) 11 | export class HeaderModule {} 12 | -------------------------------------------------------------------------------- /src/app/components/page-header/page-header.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, Input } from '@angular/core'; 2 | 3 | @Component({ 4 | selector: 'app-page-header', 5 | template: ` 6 |
7 | 8 | 9 | 10 | 11 | 12 | 13 |

{{ title }}

14 |
15 | `, 16 | }) 17 | export class PageHeaderComponent { 18 | @Input() title = ''; 19 | } 20 | -------------------------------------------------------------------------------- /src/app/components/page-header/page-header.module.ts: -------------------------------------------------------------------------------- 1 | import { CommonModule } from '@angular/common'; 2 | import { NgModule } from '@angular/core'; 3 | import { RouterModule } from '@angular/router'; 4 | import { PageHeaderComponent } from './page-header.component'; 5 | 6 | @NgModule({ 7 | declarations: [PageHeaderComponent], 8 | imports: [CommonModule, RouterModule], 9 | exports: [PageHeaderComponent], 10 | }) 11 | export class PageHeaderModule {} 12 | -------------------------------------------------------------------------------- /src/app/components/section-card/section-card.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { ComponentFixture, TestBed } from '@angular/core/testing'; 2 | 3 | import { SectionCardComponent } from './section-card.component'; 4 | 5 | describe('SectionCardComponent', () => { 6 | let component: SectionCardComponent; 7 | let fixture: ComponentFixture; 8 | 9 | beforeEach(async () => { 10 | await TestBed.configureTestingModule({ 11 | declarations: [ SectionCardComponent ] 12 | }) 13 | .compileComponents(); 14 | }); 15 | 16 | beforeEach(() => { 17 | fixture = TestBed.createComponent(SectionCardComponent); 18 | component = fixture.componentInstance; 19 | fixture.detectChanges(); 20 | }); 21 | 22 | it('should create', () => { 23 | expect(component).toBeTruthy(); 24 | }); 25 | }); 26 | -------------------------------------------------------------------------------- /src/app/components/section-card/section-card.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, Input, OnInit } from '@angular/core'; 2 | 3 | @Component({ 4 | selector: 'app-section-card', 5 | template: ` 6 |
7 | 8 |

9 | {{ data?.name }} 10 |

11 |

{{ data?.description }}

12 |
13 | `, 14 | }) 15 | export class SectionCardComponent implements OnInit { 16 | @Input() data!: { name: string; image: string; link: string; description: string }; 17 | constructor() {} 18 | 19 | ngOnInit(): void {} 20 | } 21 | -------------------------------------------------------------------------------- /src/app/components/section-card/section-card.module.ts: -------------------------------------------------------------------------------- 1 | import { CommonModule } from '@angular/common'; 2 | import { NgModule } from '@angular/core'; 3 | import { RouterModule } from '@angular/router'; 4 | import { SectionCardComponent } from './section-card.component'; 5 | 6 | @NgModule({ 7 | declarations: [SectionCardComponent], 8 | imports: [CommonModule, RouterModule], 9 | exports: [SectionCardComponent], 10 | }) 11 | export class SectionCardsModule {} 12 | -------------------------------------------------------------------------------- /src/app/components/stats-card/stats-card.component.ts: -------------------------------------------------------------------------------- 1 | import { ChangeDetectionStrategy, Component, Input } from '@angular/core'; 2 | export interface StatsCardData { 3 | name: string; 4 | value: number; 5 | delta: number; 6 | } 7 | @Component({ 8 | selector: 'app-stats-card', 9 | template: ` 10 |
11 |
12 | 13 |
14 |
15 |
16 |

{{ data?.value | currency }}

17 |

{{ data?.name }}

18 |
19 |

{{ data?.delta }}%

20 |
21 |
22 | `, 23 | styles: [ 24 | ` 25 | .negative { 26 | @apply text-red-500; 27 | } 28 | .positive { 29 | @apply text-green-500; 30 | } 31 | `, 32 | ], 33 | changeDetection: ChangeDetectionStrategy.OnPush, 34 | }) 35 | export class StatsCardComponent { 36 | @Input() data!: StatsCardData; 37 | } 38 | -------------------------------------------------------------------------------- /src/app/components/stats-card/stats-card.module.ts: -------------------------------------------------------------------------------- 1 | import { CommonModule } from '@angular/common'; 2 | import { NgModule } from '@angular/core'; 3 | import { UiStatsCardModule } from 'src/app/lib/ui-stats-card/ui-stats-card.module'; 4 | import { StatsCardComponent } from './stats-card.component'; 5 | 6 | @NgModule({ 7 | declarations: [StatsCardComponent], 8 | imports: [CommonModule, UiStatsCardModule], 9 | exports: [StatsCardComponent], 10 | }) 11 | export class StatsCardModule {} 12 | -------------------------------------------------------------------------------- /src/app/config/badges-code.config.ts: -------------------------------------------------------------------------------- 1 | import { CodeExplorerData } from '../components/code-explorer/code-explorer.component'; 2 | 3 | const CODE_TS = ` 4 | @Directive({ 5 | selector: '[badge]', 6 | }) 7 | export class UiBadgeDirective implements OnChanges, OnDestroy { 8 | @Input() badge = null; 9 | @Input() size: BadgeSizes = 'medium'; 10 | @Input() position: BadgePositions = 'top-right'; 11 | @Input() customBadgeClasses: string | null = null; 12 | @Input() variant: BadgeVariants = 'secondary'; 13 | 14 | badgeElement: HTMLElement | null = null; 15 | 16 | constructor(@Inject(DOCUMENT) private document: Document, private elRef: ElementRef) {} 17 | ngOnChanges(changes: SimpleChanges): void { 18 | if ('badge' in changes) { 19 | const value = \`\${changes.badge.currentValue}\`\.trim(); 20 | if (value?.length > 0) { 21 | this.updateBadgeText(value); 22 | } 23 | } 24 | } 25 | 26 | ngOnDestroy() { 27 | if (this.badgeElement) { 28 | this.badgeElement.remove(); 29 | } 30 | } 31 | 32 | private updateBadgeText(value: string) { 33 | if (!this.badgeElement) { 34 | this.createBadge(value); 35 | } else { 36 | this.badgeElement.textContent = value; 37 | } 38 | } 39 | 40 | private createBadge(value: string): HTMLElement { 41 | const badgeElement = this.document.createElement('span'); 42 | this.addClasses(badgeElement); 43 | badgeElement.textContent = value; 44 | this.elRef.nativeElement.classList.add('badge-container'); 45 | this.elRef.nativeElement.appendChild(badgeElement); 46 | return badgeElement; 47 | } 48 | 49 | private addClasses(badgeElement: HTMLElement) { 50 | const [vPos, hPos] = this.position.split('-'); 51 | badgeElement.classList.add('badge', vPos, hPos); 52 | if (this.customBadgeClasses) { 53 | const customClasses = this.customBadgeClasses.split(' '); 54 | badgeElement.classList.add(...customClasses); 55 | } 56 | badgeElement.classList.add(this.variant); 57 | badgeElement.classList.add(this.size); 58 | } 59 | } 60 | `; 61 | 62 | const HTML = ` 63 |
64 | 65 | 66 | 67 |
68 |
69 | 70 | 71 | 72 | 73 |
74 | `; 75 | 76 | const STYLES = ` 77 | 78 | :root { 79 | --primary: hsl(207, 94%, 49%); 80 | --primary-dark: hsl(207, 94%, 39%); 81 | --secondary: hsl(129, 87%, 22%); 82 | } 83 | 84 | /* ------- Styles for Badges Start ------- */ 85 | .badge-container { 86 | position: relative; 87 | } 88 | 89 | .badge { 90 | position: absolute; 91 | display: flex; 92 | justify-content: center; 93 | align-items: center; 94 | background-color: var(--bg-color); 95 | color: #fff; 96 | font-size: 12px; 97 | text-overflow: ellipsis; 98 | white-space: nowrap; 99 | overflow: hidden; 100 | border-radius: 50%; 101 | box-shadow: 0px 2px 6px -1px rgb(0 0 0 / 50%); 102 | } 103 | .badge.primary { 104 | --bg-color: var(--primary); 105 | } 106 | 107 | .badge.secondary { 108 | --bg-color: var(--secondary); 109 | } 110 | 111 | .badge.top { 112 | top: -10px; 113 | } 114 | .badge.bottom { 115 | bottom: -10px; 116 | } 117 | .badge.left { 118 | left: -10px; 119 | } 120 | .badge.right { 121 | right: -10px; 122 | } 123 | .badge.small { 124 | width: 18px; 125 | height: 18px; 126 | font-size: 10px; 127 | } 128 | .badge.medium { 129 | width: 22px; 130 | height: 22px; 131 | font-size: 11px; 132 | } 133 | .badge.large { 134 | width: 28px; 135 | height: 28px; 136 | font-size: 12px; 137 | } 138 | /* ------- Styles for Badges End ------- */ 139 | `; 140 | 141 | export const BADGES_CODE: CodeExplorerData[] = [ 142 | { 143 | name: 'Directive', 144 | content: CODE_TS, 145 | language: 'typescript', 146 | }, 147 | { 148 | name: 'Usage', 149 | content: HTML, 150 | language: 'html', 151 | }, 152 | { 153 | name: 'Styles', 154 | content: STYLES, 155 | language: 'scss', 156 | }, 157 | ]; 158 | -------------------------------------------------------------------------------- /src/app/config/fullscreen-code.config..ts: -------------------------------------------------------------------------------- 1 | import { CodeExplorerData } from '../components/code-explorer/code-explorer.component'; 2 | 3 | const FULLSCREEN_CODE_TS = ` 4 | @Directive({ 5 | selector: '[appUiFullscreen]', 6 | exportAs: 'fullscreen', 7 | }) 8 | export class UiFullscreenDirective { 9 | private isMaximizedSubject = new BehaviorSubject(false); 10 | isMaximized$ = this.isMaximizedSubject.asObservable(); 11 | 12 | constructor(private el: ElementRef) {} 13 | 14 | toggle() { 15 | if (this.isMaximizedSubject?.getValue()) this.minimize(); 16 | else this.maximize(); 17 | } 18 | maximize() { 19 | if (this.el) { 20 | this.isMaximizedSubject.next(true); 21 | this.nativeElement.classList.add('fullscreen'); 22 | if (Fullscreen.isEnabled) { 23 | Fullscreen.request(); 24 | } 25 | } 26 | } 27 | minimize() { 28 | if (this.el) { 29 | this.isMaximizedSubject.next(false); 30 | this.nativeElement.classList.remove('fullscreen'); 31 | if (Fullscreen.isEnabled) { 32 | Fullscreen.exit(); 33 | } 34 | } 35 | } 36 | 37 | private get nativeElement() { 38 | return this.el.nativeElement as HTMLElement; 39 | } 40 | } 41 | `; 42 | 43 | const FULLSCREEN_HTML = ` 44 |
45 |
46 |

Total Sales Report

47 |
48 | 49 | 50 |
51 |
52 |
53 |
54 | `; 55 | 56 | export const FULLSCREEN_CODE: CodeExplorerData[] = [ 57 | { 58 | name: 'Directive', 59 | content: FULLSCREEN_CODE_TS, 60 | language: 'typescript', 61 | }, 62 | { 63 | name: 'Usage', 64 | content: FULLSCREEN_HTML, 65 | language: 'html', 66 | }, 67 | ]; 68 | -------------------------------------------------------------------------------- /src/app/config/highlight-code.config.ts: -------------------------------------------------------------------------------- 1 | import { CodeExplorerData } from '../components/code-explorer/code-explorer.component'; 2 | 3 | const HIGHLIGHT_CODE_TS = ` 4 | @Directive({ 5 | selector: "[highlight]" 6 | }) 7 | export class HighlightDirective implements OnChanges { 8 | @Input("highlight") searchTerm: string; 9 | @Input() caseSensitive = false; 10 | @Input() customClasses = ""; 11 | 12 | @HostBinding("innerHtml") 13 | content: string; 14 | constructor(private el: ElementRef, private sanitizer: DomSanitizer) {} 15 | 16 | ngOnChanges(changes: SimpleChanges) { 17 | if (this.el?.nativeElement) { 18 | if ("searchTerm" in changes || "caseSensitive" in changes) { 19 | const text = (this.el.nativeElement as HTMLElement).textContent; 20 | if (this.searchTerm === "") { 21 | this.content = text; 22 | } else { 23 | let regex = new RegExp( 24 | this.searchTerm, 25 | this.caseSensitive ? "g" : "gi" 26 | ); 27 | let newText = text.replace(regex, (match: string) => { 28 | return \`\${match}\`; 29 | }); 30 | const sanitzed = this.sanitizer.sanitize( 31 | SecurityContext.HTML, 32 | newText 33 | ); 34 | this.content = sanitzed; 35 | } 36 | } 37 | } 38 | } 39 | } 40 | `; 41 | 42 | const HIGHLIGHT_HTML = ` 43 |
46 | Lorem Ipsum has been the industry's standard dummy text ever since the 47 | 1500s, when an unknown printer took a galley of type and scrambled it to 48 | make a type specimen book. 49 |
50 | `; 51 | 52 | export const HIGHLIGHT_CODE: CodeExplorerData[] = [ 53 | { 54 | name: 'Directive', 55 | content: HIGHLIGHT_CODE_TS, 56 | language: 'typescript', 57 | }, 58 | { 59 | name: 'Usage', 60 | content: HIGHLIGHT_HTML, 61 | language: 'html', 62 | }, 63 | ]; 64 | -------------------------------------------------------------------------------- /src/app/config/long-press-code.config.ts: -------------------------------------------------------------------------------- 1 | import { CodeExplorerData } from '../components/code-explorer/code-explorer.component'; 2 | 3 | const CODE_TS = ` 4 | @Directive({ 5 | selector: '[longPress]', 6 | }) 7 | export class UiLongPressDirective implements OnInit, OnDestroy { 8 | @Input() duration = 500; 9 | @Output() longPress = new EventEmitter(); 10 | 11 | sub!: Subscription; 12 | constructor(private el: ElementRef) {} 13 | ngOnInit(): void { 14 | const mouseDown$ = fromEvent(this.el.nativeElement, 'mousedown'); 15 | const mouseUp$ = fromEvent(this.el.nativeElement, 'mouseup'); 16 | this.sub = mouseDown$ 17 | .pipe(switchMap(() => timer(this.duration).pipe(takeUntil(mouseUp$)))) 18 | .subscribe(() => this.longPress.emit()); 19 | } 20 | ngOnDestroy(): void { 21 | this.sub.unsubscribe(); 22 | } 23 | } 24 | `; 25 | 26 | const HTML = ` 27 | 28 | 29 | 30 | 31 | `; 32 | 33 | export const LONG_PRESS_CODE: CodeExplorerData[] = [ 34 | { 35 | name: 'Directive', 36 | content: CODE_TS, 37 | language: 'typescript', 38 | }, 39 | { 40 | name: 'Usage', 41 | content: HTML, 42 | language: 'html', 43 | }, 44 | ]; 45 | -------------------------------------------------------------------------------- /src/app/config/permissions-code.config.ts: -------------------------------------------------------------------------------- 1 | import { CodeExplorerData } from '../components/code-explorer/code-explorer.component'; 2 | 3 | const PERMISSION_CODE_TS = ` 4 | @Directive({ 5 | selector: '[appUiPermissions]', 6 | }) 7 | export class UiPermissionsDirective implements OnInit, OnDestroy { 8 | private loggedInUser!: User; 9 | private permission!: Permissions; 10 | private feature!: string; 11 | 12 | subscription!: Subscription; 13 | 14 | @Input() 15 | set appUiPermissions(permission: Permissions) { 16 | this.permission = permission; 17 | this.updateView(); 18 | } 19 | 20 | @Input() 21 | set appUiPermissionsFeature(feature: string) { 22 | this.feature = feature; 23 | this.updateView(); 24 | } 25 | constructor(private tpl: TemplateRef, private vcr: ViewContainerRef, private authService: AuthService) {} 26 | 27 | ngOnInit() { 28 | this.subscription = this.authService.loggedUser$.subscribe((user) => { 29 | this.loggedInUser = user; 30 | this.vcr.clear(); 31 | this.updateView(); 32 | }); 33 | } 34 | 35 | ngOnDestroy() { 36 | this.subscription.unsubscribe(); 37 | } 38 | 39 | private updateView() { 40 | if (this.hasPermission()) { 41 | this.vcr.createEmbeddedView(this.tpl); 42 | } else { 43 | this.vcr.clear(); 44 | } 45 | } 46 | 47 | private hasPermission() { 48 | if (!this.loggedInUser) return false; 49 | const featurePermissions = this.loggedInUser.permissions[this.feature]; 50 | if (featurePermissions) { 51 | return featurePermissions.includes(this.permission); 52 | } 53 | return false; 54 | } 55 | } 56 | `; 57 | 58 | const PERMISSION_HTML = ` 59 |
60 | 61 | 62 | 63 |
64 | `; 65 | 66 | export const PERMISSION_CODE: CodeExplorerData[] = [ 67 | { 68 | name: 'Directive', 69 | content: PERMISSION_CODE_TS, 70 | language: 'typescript', 71 | }, 72 | { 73 | name: 'Usage', 74 | content: PERMISSION_HTML, 75 | language: 'html', 76 | }, 77 | ]; 78 | -------------------------------------------------------------------------------- /src/app/config/permissions.config.ts: -------------------------------------------------------------------------------- 1 | import { Permissions, User } from '../interfaces/user.interface'; 2 | 3 | export const AUTHORIZED_USER: User = { 4 | id: 1, 5 | name: 'Leanne Graham', 6 | username: 'Bret', 7 | email: 'sincere@april.biz', 8 | permissions: { 9 | product: [Permissions.create, Permissions.update, Permissions.delete, Permissions.read], 10 | }, 11 | }; 12 | export const UNAUTHORIZED_USER = { 13 | id: 2, 14 | name: 'Ervin Howell', 15 | username: 'Antonette', 16 | email: 'shanna@melissa.tv', 17 | permissions: { 18 | product: [Permissions.read], 19 | }, 20 | }; 21 | -------------------------------------------------------------------------------- /src/app/config/sections.config.ts: -------------------------------------------------------------------------------- 1 | export const SECTIONS = [ 2 | { 3 | name: 'Simple Stats Cards', 4 | image: 'stats-card', 5 | link: 'stats', 6 | description: `A simple directive to modify the styles of the change value and add 7 | an UP/DOWN arrow based on the change value. 8 | `, 9 | }, 10 | { 11 | name: 'Fullscreen Toggle', 12 | image: 'fullscreen', 13 | link: 'fullscreen', 14 | description: 'A directive that can help add maximize/minimize functionality to a component', 15 | }, 16 | { 17 | name: 'Permissions Directive', 18 | image: 'permissions', 19 | link: 'permissions', 20 | description: 'A structural directive that can be used to display items only if they are authorized to view them.', 21 | }, 22 | { 23 | name: 'Highlight Directive', 24 | image: 'highlight', 25 | link: 'highlight', 26 | description: 27 | 'A simple directive that can be used to highlight text in a paragraph. Useful for highlighting search matches.', 28 | }, 29 | { 30 | name: 'Long Press Directive', 31 | image: 'long-press', 32 | link: 'long-press', 33 | description: 34 | 'Add long press behavior to elements with this directive. Comes with configurable long press delay time.', 35 | }, 36 | { 37 | name: 'Badge Directive', 38 | image: 'badges', 39 | link: 'badges', 40 | description: 'Super simple directive to add badges to your elements. Customize the position and sizes.', 41 | }, 42 | { 43 | name: 'Table Sort Directive', 44 | image: 'table-sort', 45 | link: 'table-sort', 46 | description: 'Outsource the logic for sorting into a directive. This way the table component would be leaner.', 47 | }, 48 | ]; 49 | -------------------------------------------------------------------------------- /src/app/config/stats-code.config.ts: -------------------------------------------------------------------------------- 1 | import { CodeExplorerData } from '../components/code-explorer/code-explorer.component'; 2 | 3 | const STATS_CODE_TS = ` 4 | @Directive({ 5 | selector: '[appStatsDeltaColorArrow]', 6 | }) 7 | export class StatsDeltaColorArrowDirective implements OnChanges { 8 | @Input() value!: number; 9 | 10 | private arrow!: HTMLElement; 11 | 12 | @HostBinding('class') 13 | get classes() { 14 | return this.value > 0 ? 'positive' : 'negative'; 15 | } 16 | constructor(@Inject(DOCUMENT) private document: Document, private el: ElementRef) {} 17 | 18 | ngOnChanges(changes: SimpleChanges): void { 19 | const currentValue = changes.value.currentValue; 20 | if ('value' in changes && currentValue != undefined) { 21 | const el = this.el.nativeElement as HTMLElement; 22 | if (!this.arrow) { 23 | this.arrow = this.getArrowElement(currentValue); 24 | el.appendChild(this.arrow); 25 | } 26 | } 27 | } 28 | 29 | getArrowElement(value: number) { 30 | const arrow = this.document.createElement('span'); 31 | arrow.style.setProperty('margin-left', '4px'); 32 | arrow.textContent = value > 0 ? '⮝' : '⮟'; 33 | return arrow; 34 | } 35 | } 36 | `; 37 | 38 | const STATS_HTML = ` 39 |
40 |
41 | 42 |
43 |
44 |
45 |

{{ data?.value | currency }}

46 |

{{ data?.name }}

47 |
48 |

{{ data?.delta }}%

49 |
50 |
51 | `; 52 | 53 | export const STATS_CODE: CodeExplorerData[] = [ 54 | { 55 | name: 'Directive', 56 | content: STATS_CODE_TS, 57 | language: 'typescript', 58 | }, 59 | { 60 | name: 'Usage', 61 | content: STATS_HTML, 62 | language: 'html', 63 | }, 64 | ]; 65 | -------------------------------------------------------------------------------- /src/app/config/table-sort-code.config.ts: -------------------------------------------------------------------------------- 1 | import { CodeExplorerData } from '../components/code-explorer/code-explorer.component'; 2 | 3 | const PARENT_DIRECTIVE = ` 4 | @Directive({ 5 | selector: '[sorter]', 6 | }) 7 | export class Sorter { 8 | active: string | null = null; 9 | direction: SortDirection = null; 10 | @Output() sortChange = new EventEmitter(); 11 | 12 | sort(column: string) { 13 | let direction = this.direction; 14 | if (this.active !== column) { 15 | this.direction = null; 16 | this.active = column; 17 | } 18 | if (this.direction === null) { 19 | direction = 'asc'; 20 | } else if (this.direction === 'asc') { 21 | direction = 'desc'; 22 | } else if (this.direction === 'desc') { 23 | direction = null; 24 | } 25 | this.sortChange.emit({ 26 | column, 27 | direction, 28 | }); 29 | this.direction = direction; 30 | } 31 | } 32 | `; 33 | 34 | const HEADER = ` 35 | @Component({ 36 | selector: '[sortHeader]', 37 | template: \`\ 38 |
39 | 40 |
48 | 🡡 49 |
50 |
51 | \`\, 52 | styles: [ 53 | \`\ 54 | .sort-col { 55 | display: flex; 56 | justify-content: space-between; 57 | align-items: center; 58 | } 59 | .arrow { 60 | font-size: 14px; 61 | } 62 | .arrow.hide { 63 | opacity: 0; 64 | } 65 | .arrow.desc { 66 | transform: rotate(180deg); 67 | } 68 | \`\, 69 | ], 70 | }) 71 | export class SortHeader { 72 | @Input() 73 | ref: string | null = null; 74 | 75 | @HostListener('click') 76 | sort() { 77 | if (!this.ref) { 78 | throw new Error('ref should be provided'); 79 | } 80 | this.sorter.sort(this.ref); 81 | } 82 | constructor(public sorter: Sorter) {} 83 | } 84 | `; 85 | 86 | 87 | const HTML = ` 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 |
First nameLast nameBirthday
{{user?.firstname}}
{{user?.lastname}}
{{user?.birthday}}
104 | `; 105 | 106 | export const TABLE_SORT_CODE: CodeExplorerData[] = [ 107 | { 108 | name: 'Main Directive', 109 | content: PARENT_DIRECTIVE, 110 | language: 'typescript', 111 | }, 112 | { 113 | name: 'Sort Header', 114 | content: HEADER, 115 | language: 'typescript', 116 | }, 117 | { 118 | name: 'Usage', 119 | content: HTML, 120 | language: 'html', 121 | }, 122 | ]; 123 | -------------------------------------------------------------------------------- /src/app/interfaces/user.interface.ts: -------------------------------------------------------------------------------- 1 | export interface User { 2 | id: number; 3 | name: string; 4 | username: string; 5 | email: string; 6 | permissions: UserPermission; 7 | } 8 | 9 | export interface UserPermission { 10 | [key: string]: Permissions[]; 11 | } 12 | 13 | export enum Permissions { 14 | create = 'CREATE', 15 | read = 'READ', 16 | update = 'UPDATE', 17 | delete = 'DELETE', 18 | } 19 | -------------------------------------------------------------------------------- /src/app/lib/ui-badge/badge.interface.ts: -------------------------------------------------------------------------------- 1 | export type BadgeSizes = 'small' | 'medium' | 'large'; 2 | 3 | export type BadgePositions = 'top-right' | 'top-left' | 'bottom-right' | 'bottom-left'; 4 | 5 | export type BadgeVariants = 'primary' | 'secondary'; 6 | -------------------------------------------------------------------------------- /src/app/lib/ui-badge/badge.styles.scss: -------------------------------------------------------------------------------- 1 | 2 | :root { 3 | --primary: hsl(207, 94%, 49%); 4 | --primary-dark: hsl(207, 94%, 39%); 5 | --secondary: hsl(129, 87%, 22%); 6 | } 7 | 8 | /* ------- Styles for Badges Start ------- */ 9 | .badge-container { 10 | position: relative; 11 | } 12 | 13 | .badge { 14 | position: absolute; 15 | display: flex; 16 | justify-content: center; 17 | align-items: center; 18 | background-color: var(--bg-color); 19 | color: #fff; 20 | font-size: 12px; 21 | text-overflow: ellipsis; 22 | white-space: nowrap; 23 | overflow: hidden; 24 | border-radius: 50%; 25 | box-shadow: 0px 2px 6px -1px rgb(0 0 0 / 50%); 26 | } 27 | .badge.primary { 28 | --bg-color: var(--primary); 29 | } 30 | 31 | .badge.secondary { 32 | --bg-color: var(--secondary); 33 | } 34 | 35 | .badge.top { 36 | top: -10px; 37 | } 38 | .badge.bottom { 39 | bottom: -10px; 40 | } 41 | .badge.left { 42 | left: -10px; 43 | } 44 | .badge.right { 45 | right: -10px; 46 | } 47 | .badge.small { 48 | width: 18px; 49 | height: 18px; 50 | font-size: 10px; 51 | } 52 | .badge.medium { 53 | width: 22px; 54 | height: 22px; 55 | font-size: 11px; 56 | } 57 | .badge.large { 58 | width: 28px; 59 | height: 28px; 60 | font-size: 12px; 61 | } 62 | /* ------- Styles for Badges End ------- */ -------------------------------------------------------------------------------- /src/app/lib/ui-badge/ui-badge.directive.ts: -------------------------------------------------------------------------------- 1 | import { DOCUMENT } from '@angular/common'; 2 | import { Directive, ElementRef, Inject, Input, OnChanges, OnDestroy, SimpleChanges } from '@angular/core'; 3 | import { BadgePositions, BadgeSizes, BadgeVariants } from './badge.interface'; 4 | 5 | @Directive({ 6 | selector: '[badge]', 7 | }) 8 | export class UiBadgeDirective implements OnChanges, OnDestroy { 9 | @Input() badge: string | null = null; 10 | @Input() size: BadgeSizes = 'medium'; 11 | @Input() position: BadgePositions = 'top-right'; 12 | @Input() customBadgeClasses: string | null = null; 13 | @Input() variant: BadgeVariants = 'secondary'; 14 | 15 | badgeElement: HTMLElement | null = null; 16 | 17 | constructor(@Inject(DOCUMENT) private document: Document, private elRef: ElementRef) {} 18 | ngOnChanges(changes: SimpleChanges): void { 19 | if ('badge' in changes) { 20 | const value = `${changes.badge.currentValue}`.trim(); 21 | if (value?.length > 0) { 22 | this.updateBadgeText(value); 23 | } 24 | } 25 | } 26 | 27 | ngOnDestroy() { 28 | if (this.badgeElement) { 29 | this.badgeElement.remove(); 30 | } 31 | } 32 | 33 | private updateBadgeText(value: string) { 34 | if (!this.badgeElement) { 35 | this.createBadge(value); 36 | } else { 37 | this.badgeElement.textContent = value; 38 | } 39 | } 40 | 41 | private createBadge(value: string): HTMLElement { 42 | const badgeElement = this.document.createElement('span'); 43 | this.addClasses(badgeElement); 44 | badgeElement.textContent = value; 45 | this.elRef.nativeElement.classList.add('badge-container'); 46 | this.elRef.nativeElement.appendChild(badgeElement); 47 | return badgeElement; 48 | } 49 | 50 | private addClasses(badgeElement: HTMLElement) { 51 | const [vPos, hPos] = this.position.split('-'); 52 | badgeElement.classList.add('badge', vPos, hPos); 53 | if (this.customBadgeClasses) { 54 | const customClasses = this.customBadgeClasses.split(' '); 55 | badgeElement.classList.add(...customClasses); 56 | } 57 | badgeElement.classList.add(this.variant); 58 | badgeElement.classList.add(this.size); 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /src/app/lib/ui-badge/ui-badge.module.ts: -------------------------------------------------------------------------------- 1 | import { CommonModule } from '@angular/common'; 2 | import { NgModule } from '@angular/core'; 3 | import { UiBadgeDirective } from './ui-badge.directive'; 4 | 5 | @NgModule({ 6 | declarations: [UiBadgeDirective], 7 | imports: [CommonModule], 8 | exports: [UiBadgeDirective], 9 | }) 10 | export class UiBadgeModule {} 11 | -------------------------------------------------------------------------------- /src/app/lib/ui-buttons/ui-buttons.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { CommonModule } from '@angular/common'; 3 | 4 | 5 | 6 | @NgModule({ 7 | declarations: [], 8 | imports: [ 9 | CommonModule 10 | ] 11 | }) 12 | export class UiButtonsModule { } 13 | -------------------------------------------------------------------------------- /src/app/lib/ui-fullscreen/ui-fullscreen.directive.ts: -------------------------------------------------------------------------------- 1 | import { Directive, ElementRef } from '@angular/core'; 2 | import { BehaviorSubject } from 'rxjs'; 3 | import * as Fullscreen from 'screenfull'; 4 | 5 | @Directive({ 6 | selector: '[appUiFullscreen]', 7 | exportAs: 'fullscreen', 8 | }) 9 | export class UiFullscreenDirective { 10 | private isMaximizedSubject = new BehaviorSubject(false); 11 | isMaximized$ = this.isMaximizedSubject.asObservable(); 12 | 13 | constructor(private el: ElementRef) {} 14 | 15 | toggle() { 16 | if (this.isMaximizedSubject?.getValue()) this.minimize(); 17 | else this.maximize(); 18 | } 19 | maximize() { 20 | if (this.el) { 21 | this.isMaximizedSubject.next(true); 22 | this.nativeElement.classList.add('fullscreen'); 23 | if (Fullscreen.isEnabled) { 24 | Fullscreen.request(); 25 | } 26 | } 27 | } 28 | minimize() { 29 | if (this.el) { 30 | this.isMaximizedSubject.next(false); 31 | this.nativeElement.classList.remove('fullscreen'); 32 | if (Fullscreen.isEnabled) { 33 | Fullscreen.exit(); 34 | } 35 | } 36 | } 37 | 38 | private get nativeElement() { 39 | return this.el.nativeElement as HTMLElement; 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/app/lib/ui-fullscreen/ui-fullscreen.module.ts: -------------------------------------------------------------------------------- 1 | import { CommonModule } from '@angular/common'; 2 | import { NgModule } from '@angular/core'; 3 | import { UiFullscreenDirective } from './ui-fullscreen.directive'; 4 | 5 | @NgModule({ 6 | declarations: [UiFullscreenDirective], 7 | imports: [CommonModule], 8 | exports: [UiFullscreenDirective], 9 | }) 10 | export class UiFullscreenModule {} 11 | -------------------------------------------------------------------------------- /src/app/lib/ui-highlight/ui-highlight.directive.ts: -------------------------------------------------------------------------------- 1 | import { Directive, ElementRef, HostBinding, Input, OnChanges, SecurityContext, SimpleChanges } from '@angular/core'; 2 | import { DomSanitizer } from '@angular/platform-browser'; 3 | 4 | @Directive({ 5 | selector: '[appUiHighlight]', 6 | }) 7 | export class UiHighlightDirective implements OnChanges { 8 | @Input('appUiHighlight') searchTerm!: string; 9 | @Input() caseSensitive = false; 10 | @Input() customClasses = ''; 11 | 12 | @HostBinding('innerHtml') 13 | content!: string; 14 | constructor(private el: ElementRef, private sanitizer: DomSanitizer) {} 15 | 16 | ngOnChanges(changes: SimpleChanges) { 17 | if (this.el?.nativeElement) { 18 | if ('searchTerm' in changes || 'caseSensitive' in changes) { 19 | const text = (this.el.nativeElement as HTMLElement).textContent ?? ''; 20 | if (this.searchTerm === '') { 21 | this.content = text; 22 | } else { 23 | let regex = new RegExp(this.searchTerm, this.caseSensitive ? 'g' : 'gi'); 24 | let newText = text.replace(regex, (match: string) => { 25 | return `${match}`; 26 | }); 27 | const sanitized = this.sanitizer.sanitize(SecurityContext.HTML, newText); 28 | if (sanitized) this.content = sanitized; 29 | } 30 | } 31 | } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/app/lib/ui-highlight/ui-highlight.module.ts: -------------------------------------------------------------------------------- 1 | import { CommonModule } from '@angular/common'; 2 | import { NgModule } from '@angular/core'; 3 | import { UiHighlightDirective } from './ui-highlight.directive'; 4 | 5 | @NgModule({ 6 | declarations: [UiHighlightDirective], 7 | imports: [CommonModule], 8 | exports: [UiHighlightDirective], 9 | }) 10 | export class UiHighlightModule {} 11 | -------------------------------------------------------------------------------- /src/app/lib/ui-long-press/ui-long-press.directive.ts: -------------------------------------------------------------------------------- 1 | import { Directive, ElementRef, EventEmitter, Input, OnDestroy, OnInit, Output } from '@angular/core'; 2 | import { fromEvent, Subscription, timer } from 'rxjs'; 3 | import { switchMap, takeUntil } from 'rxjs/operators'; 4 | 5 | @Directive({ 6 | selector: '[longPress]', 7 | }) 8 | export class UiLongPressDirective implements OnInit, OnDestroy { 9 | @Input() duration = 500; 10 | @Output() longPress = new EventEmitter(); 11 | 12 | sub!: Subscription; 13 | constructor(private el: ElementRef) {} 14 | ngOnInit(): void { 15 | const mouseDown$ = fromEvent(this.el.nativeElement, 'mousedown'); 16 | const mouseUp$ = fromEvent(this.el.nativeElement, 'mouseup'); 17 | this.sub = mouseDown$ 18 | .pipe(switchMap(() => timer(this.duration).pipe(takeUntil(mouseUp$)))) 19 | .subscribe(() => this.longPress.emit()); 20 | } 21 | ngOnDestroy(): void { 22 | this.sub.unsubscribe(); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/app/lib/ui-long-press/ui-long-press.module.ts: -------------------------------------------------------------------------------- 1 | import { CommonModule } from '@angular/common'; 2 | import { NgModule } from '@angular/core'; 3 | import { UiLongPressDirective } from './ui-long-press.directive'; 4 | 5 | @NgModule({ 6 | declarations: [UiLongPressDirective], 7 | imports: [CommonModule], 8 | exports: [UiLongPressDirective], 9 | }) 10 | export class UiLongPressModule {} 11 | -------------------------------------------------------------------------------- /src/app/lib/ui-permissions/ui-permissions.directive.ts: -------------------------------------------------------------------------------- 1 | import { Directive, Input, OnDestroy, OnInit, TemplateRef, ViewContainerRef } from '@angular/core'; 2 | import { Subscription } from 'rxjs'; 3 | import { Permissions, User } from 'src/app/interfaces/user.interface'; 4 | import { AuthService } from 'src/app/services/auth.service'; 5 | 6 | @Directive({ 7 | selector: '[appUiPermissions]', 8 | }) 9 | export class UiPermissionsDirective implements OnInit, OnDestroy { 10 | private loggedInUser!: User; 11 | private permission!: Permissions; 12 | private feature!: string; 13 | 14 | private subscription!: Subscription; 15 | 16 | @Input() 17 | set appUiPermissions(permission: any) { 18 | this.permission = permission; 19 | this.updateView(); 20 | } 21 | 22 | @Input() 23 | set appUiPermissionsFeature(feature: string) { 24 | this.feature = feature; 25 | this.updateView(); 26 | } 27 | constructor(private tpl: TemplateRef, private vcr: ViewContainerRef, private authService: AuthService) {} 28 | 29 | ngOnInit() { 30 | this.subscription = this.authService.loggedUser$.subscribe((user) => { 31 | this.loggedInUser = user; 32 | this.updateView(); 33 | }); 34 | } 35 | 36 | ngOnDestroy() { 37 | this.subscription.unsubscribe(); 38 | } 39 | 40 | private updateView() { 41 | this.vcr.clear(); 42 | if (this.hasPermission()) { 43 | this.vcr.createEmbeddedView(this.tpl); 44 | } else { 45 | this.vcr.clear(); 46 | } 47 | } 48 | 49 | private hasPermission() { 50 | if (!this.loggedInUser) return false; 51 | const featurePermissions = this.loggedInUser.permissions[this.feature]; 52 | if (featurePermissions) { 53 | return featurePermissions.includes(this.permission); 54 | } 55 | return false; 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /src/app/lib/ui-permissions/ui-permissions.module.ts: -------------------------------------------------------------------------------- 1 | import { CommonModule } from '@angular/common'; 2 | import { NgModule } from '@angular/core'; 3 | import { UiPermissionsDirective } from './ui-permissions.directive'; 4 | 5 | @NgModule({ 6 | declarations: [UiPermissionsDirective], 7 | imports: [CommonModule], 8 | exports: [UiPermissionsDirective], 9 | }) 10 | export class UiPermissionsModule {} 11 | -------------------------------------------------------------------------------- /src/app/lib/ui-stats-card/stats-delta-color-arrow.directive.ts: -------------------------------------------------------------------------------- 1 | import { DOCUMENT } from '@angular/common'; 2 | import { Directive, ElementRef, HostBinding, Inject, Input, OnChanges, SimpleChanges } from '@angular/core'; 3 | 4 | @Directive({ 5 | selector: '[appStatsDeltaColorArrow]', 6 | }) 7 | export class StatsDeltaColorArrowDirective implements OnChanges { 8 | @Input() value!: number; 9 | 10 | private arrow!: HTMLElement; 11 | 12 | @HostBinding('class') 13 | get classes() { 14 | return this.value > 0 ? 'positive' : 'negative'; 15 | } 16 | constructor(@Inject(DOCUMENT) private document: Document, private el: ElementRef) {} 17 | 18 | ngOnChanges(changes: SimpleChanges): void { 19 | const currentValue = changes.value.currentValue; 20 | if ('value' in changes && currentValue != undefined) { 21 | const el = this.el.nativeElement as HTMLElement; 22 | if (!this.arrow) { 23 | this.arrow = this.getArrowElement(currentValue); 24 | el.appendChild(this.arrow); 25 | } 26 | } 27 | } 28 | 29 | getArrowElement(value: number) { 30 | const arrow = this.document.createElement('span'); 31 | arrow.style.setProperty('margin-left', '4px'); 32 | arrow.textContent = value > 0 ? '⮝' : '⮟'; 33 | return arrow; 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/app/lib/ui-stats-card/stats-delta-color.directive.ts: -------------------------------------------------------------------------------- 1 | import { Directive, HostBinding, Input } from '@angular/core'; 2 | 3 | @Directive({ 4 | selector: '[appStatsDeltaColor]', 5 | }) 6 | export class StatsDeltaColorDirective { 7 | @Input() value!: number; 8 | 9 | @HostBinding('class') 10 | get classes() { 11 | return this.value > 0 ? 'positive' : 'negative'; 12 | } 13 | constructor() {} 14 | } 15 | -------------------------------------------------------------------------------- /src/app/lib/ui-stats-card/ui-stats-card.module.ts: -------------------------------------------------------------------------------- 1 | import { CommonModule } from '@angular/common'; 2 | import { NgModule } from '@angular/core'; 3 | import { StatsDeltaColorArrowDirective } from './stats-delta-color-arrow.directive'; 4 | import { StatsDeltaColorDirective } from './stats-delta-color.directive'; 5 | 6 | @NgModule({ 7 | declarations: [StatsDeltaColorDirective, StatsDeltaColorArrowDirective], 8 | imports: [CommonModule], 9 | exports: [StatsDeltaColorDirective, StatsDeltaColorArrowDirective], 10 | }) 11 | export class UiStatsCardModule {} 12 | -------------------------------------------------------------------------------- /src/app/lib/ui-table-sort/ui-table-sort.directive.ts: -------------------------------------------------------------------------------- 1 | import { Component, Directive, EventEmitter, HostListener, Input, Output } from '@angular/core'; 2 | export interface SortChangeEvent { 3 | column: string; 4 | direction: SortDirection; 5 | } 6 | 7 | type SortDirection = 'asc' | 'desc' | null; 8 | @Directive({ 9 | selector: '[sorter]', 10 | }) 11 | export class Sorter { 12 | active: string | null = null; 13 | direction: SortDirection = null; 14 | @Output() sortChange = new EventEmitter(); 15 | 16 | sort(column: string) { 17 | let direction = this.direction; 18 | if (this.active !== column) { 19 | this.direction = null; 20 | this.active = column; 21 | } 22 | if (this.direction === null) { 23 | direction = 'asc'; 24 | } else if (this.direction === 'asc') { 25 | direction = 'desc'; 26 | } else if (this.direction === 'desc') { 27 | direction = null; 28 | } 29 | this.sortChange.emit({ 30 | column, 31 | direction, 32 | }); 33 | this.direction = direction; 34 | } 35 | } 36 | 37 | @Component({ 38 | selector: '[sortHeader]', 39 | template: ` 40 |
41 | 42 |
50 | 🡡 51 |
52 |
53 | `, 54 | styles: [ 55 | ` 56 | .sort-col { 57 | display: flex; 58 | justify-content: space-between; 59 | align-items: center; 60 | } 61 | .arrow { 62 | font-size: 14px; 63 | } 64 | .arrow.hide { 65 | opacity: 0; 66 | } 67 | .arrow.desc { 68 | transform: rotate(180deg); 69 | } 70 | `, 71 | ], 72 | }) 73 | export class SortHeader { 74 | @Input() 75 | ref: string | null = null; 76 | 77 | @HostListener('click') 78 | sort() { 79 | if (!this.ref) { 80 | throw new Error('ref should be provided'); 81 | } 82 | this.sorter.sort(this.ref); 83 | } 84 | constructor(public sorter: Sorter) {} 85 | } 86 | -------------------------------------------------------------------------------- /src/app/lib/ui-table-sort/ui-table-sort.module.ts: -------------------------------------------------------------------------------- 1 | import { CommonModule } from '@angular/common'; 2 | import { NgModule } from '@angular/core'; 3 | import { Sorter, SortHeader } from './ui-table-sort.directive'; 4 | 5 | @NgModule({ 6 | declarations: [SortHeader, Sorter], 7 | imports: [CommonModule], 8 | exports: [SortHeader, Sorter], 9 | }) 10 | export class UiTableSortModule {} 11 | -------------------------------------------------------------------------------- /src/app/pages/badges/badges-routing.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { RouterModule, Routes } from '@angular/router'; 3 | import { BadgesComponent } from './badges.component'; 4 | 5 | const routes: Routes = [{ path: '', component: BadgesComponent }]; 6 | 7 | @NgModule({ 8 | imports: [RouterModule.forChild(routes)], 9 | exports: [RouterModule] 10 | }) 11 | export class BadgesRoutingModule { } 12 | -------------------------------------------------------------------------------- /src/app/pages/badges/badges.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit } from '@angular/core'; 2 | import { BADGES_CODE } from '@config/badges-code.config'; 3 | 4 | @Component({ 5 | selector: 'app-badges', 6 | template: ` 7 |
8 |
9 | 10 | 11 | 12 |
13 |
14 | 15 | 16 | 17 | 18 |
19 |
20 |
21 | 22 |
`, 23 | }) 24 | export class BadgesComponent implements OnInit { 25 | code = BADGES_CODE; 26 | constructor() {} 27 | 28 | ngOnInit(): void {} 29 | } 30 | -------------------------------------------------------------------------------- /src/app/pages/badges/badges.module.ts: -------------------------------------------------------------------------------- 1 | import { CommonModule } from '@angular/common'; 2 | import { NgModule } from '@angular/core'; 3 | import { CodeExplorerModule } from '@components/code-explorer/code-explorer.module'; 4 | import { PageHeaderModule } from '@components/page-header/page-header.module'; 5 | import { UiBadgeModule } from '@lib/ui-badge/ui-badge.module'; 6 | import { BadgesRoutingModule } from './badges-routing.module'; 7 | import { BadgesComponent } from './badges.component'; 8 | 9 | @NgModule({ 10 | declarations: [BadgesComponent], 11 | imports: [CommonModule, BadgesRoutingModule, UiBadgeModule, CodeExplorerModule, PageHeaderModule], 12 | }) 13 | export class BadgesModule {} 14 | -------------------------------------------------------------------------------- /src/app/pages/fullscreen/fullscreen-routing.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { RouterModule, Routes } from '@angular/router'; 3 | import { FullscreenComponent } from './fullscreen.component'; 4 | 5 | const routes: Routes = [{ path: '', component: FullscreenComponent }]; 6 | 7 | @NgModule({ 8 | imports: [RouterModule.forChild(routes)], 9 | exports: [RouterModule] 10 | }) 11 | export class FullscreenRoutingModule { } 12 | -------------------------------------------------------------------------------- /src/app/pages/fullscreen/fullscreen.component.ts: -------------------------------------------------------------------------------- 1 | import { ChangeDetectionStrategy, Component } from '@angular/core'; 2 | import { FULLSCREEN_CODE } from '@config/fullscreen-code.config.'; 3 | 4 | @Component({ 5 | selector: 'app-fullscreen', 6 | template: ` 7 | 8 |
9 |
12 |

Total Sales Report

13 |
14 | 22 |
23 |
24 |
25 |
26 |
27 | 28 |
29 | `, 30 | changeDetection: ChangeDetectionStrategy.OnPush, 31 | }) 32 | export class FullscreenComponent { 33 | codes = FULLSCREEN_CODE; 34 | } 35 | -------------------------------------------------------------------------------- /src/app/pages/fullscreen/fullscreen.module.ts: -------------------------------------------------------------------------------- 1 | import { CommonModule } from '@angular/common'; 2 | import { NgModule } from '@angular/core'; 3 | import { CodeExplorerModule } from '@components/code-explorer/code-explorer.module'; 4 | import { PageHeaderModule } from '@components/page-header/page-header.module'; 5 | import { UiFullscreenModule } from '@lib/ui-fullscreen/ui-fullscreen.module'; 6 | import { FullscreenRoutingModule } from './fullscreen-routing.module'; 7 | import { FullscreenComponent } from './fullscreen.component'; 8 | 9 | @NgModule({ 10 | declarations: [FullscreenComponent], 11 | imports: [CommonModule, FullscreenRoutingModule, PageHeaderModule, UiFullscreenModule, CodeExplorerModule], 12 | }) 13 | export class FullscreenModule {} 14 | -------------------------------------------------------------------------------- /src/app/pages/highlight/highlight-routing.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { RouterModule, Routes } from '@angular/router'; 3 | import { HighlightComponent } from './highlight.component'; 4 | 5 | const routes: Routes = [{ path: '', component: HighlightComponent }]; 6 | 7 | @NgModule({ 8 | imports: [RouterModule.forChild(routes)], 9 | exports: [RouterModule] 10 | }) 11 | export class HighlightRoutingModule { } 12 | -------------------------------------------------------------------------------- /src/app/pages/highlight/highlight.component.ts: -------------------------------------------------------------------------------- 1 | import { ChangeDetectionStrategy, Component } from '@angular/core'; 2 | import { HIGHLIGHT_CODE } from '@config/highlight-code.config'; 3 | 4 | @Component({ 5 | selector: 'app-highlight', 6 | template: ` 7 | 8 |
12 |
13 | 14 | 18 |
19 |

24 | Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a 25 | galley of type and scrambled it to make a type specimen book. 26 |

27 | 28 | Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a 29 | galley of type and scrambled it to make a type specimen book. 30 | 31 | 32 |
33 | Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a 34 | galley of type and scrambled it to make a type specimen book. 35 |
36 |
37 |
38 | 39 |
40 | `, 41 | changeDetection: ChangeDetectionStrategy.OnPush, 42 | }) 43 | export class HighlightComponent { 44 | codes = HIGHLIGHT_CODE; 45 | searchTerm = 'lorem ipsum'; 46 | caseSensitive = false; 47 | } 48 | -------------------------------------------------------------------------------- /src/app/pages/highlight/highlight.module.ts: -------------------------------------------------------------------------------- 1 | import { CommonModule } from '@angular/common'; 2 | import { NgModule } from '@angular/core'; 3 | import { FormsModule } from '@angular/forms'; 4 | import { CodeExplorerModule } from '@components/code-explorer/code-explorer.module'; 5 | import { PageHeaderModule } from '@components/page-header/page-header.module'; 6 | import { UiHighlightModule } from '@lib/ui-highlight/ui-highlight.module'; 7 | import { HighlightRoutingModule } from './highlight-routing.module'; 8 | import { HighlightComponent } from './highlight.component'; 9 | 10 | @NgModule({ 11 | declarations: [HighlightComponent], 12 | imports: [CommonModule, HighlightRoutingModule, PageHeaderModule, CodeExplorerModule, UiHighlightModule, FormsModule], 13 | }) 14 | export class HighlightModule {} 15 | -------------------------------------------------------------------------------- /src/app/pages/home/home.component.ts: -------------------------------------------------------------------------------- 1 | import { ChangeDetectionStrategy, Component, OnInit } from '@angular/core'; 2 | import { SECTIONS } from '@config/sections.config'; 3 | 4 | @Component({ 5 | selector: 'app-home', 6 | template: ` 7 |
8 | 9 | 10 | 11 |
12 | `, 13 | changeDetection: ChangeDetectionStrategy.OnPush, 14 | }) 15 | export class HomeComponent implements OnInit { 16 | sections = SECTIONS; 17 | constructor() {} 18 | 19 | ngOnInit(): void {} 20 | } 21 | -------------------------------------------------------------------------------- /src/app/pages/long-press/long-press-routing.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { RouterModule, Routes } from '@angular/router'; 3 | import { LongPressComponent } from './long-press.component'; 4 | 5 | const routes: Routes = [{ path: '', component: LongPressComponent }]; 6 | 7 | @NgModule({ 8 | imports: [RouterModule.forChild(routes)], 9 | exports: [RouterModule] 10 | }) 11 | export class LongPressRoutingModule { } 12 | -------------------------------------------------------------------------------- /src/app/pages/long-press/long-press.component.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | import { LONG_PRESS_CODE } from '@config/long-press-code.config'; 3 | 4 | @Component({ 5 | selector: 'app-long-press', 6 | template: ` 7 | 8 |
9 | 10 | 11 |
12 |
13 | 14 |
15 | `, 16 | }) 17 | export class LongPressComponent { 18 | code = LONG_PRESS_CODE; 19 | handleLongPress() { 20 | alert('Successfully unlocked'); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/app/pages/long-press/long-press.module.ts: -------------------------------------------------------------------------------- 1 | import { CommonModule } from '@angular/common'; 2 | import { NgModule } from '@angular/core'; 3 | import { CodeExplorerModule } from '@components/code-explorer/code-explorer.module'; 4 | import { PageHeaderModule } from '@components/page-header/page-header.module'; 5 | import { UiLongPressModule } from '@lib/ui-long-press/ui-long-press.module'; 6 | import { LongPressRoutingModule } from './long-press-routing.module'; 7 | import { LongPressComponent } from './long-press.component'; 8 | 9 | @NgModule({ 10 | declarations: [LongPressComponent], 11 | imports: [CommonModule, LongPressRoutingModule, UiLongPressModule, CodeExplorerModule, PageHeaderModule], 12 | }) 13 | export class LongPressModule {} 14 | -------------------------------------------------------------------------------- /src/app/pages/permissions/permissions-routing.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { RouterModule, Routes } from '@angular/router'; 3 | import { PermissionsComponent } from './permissions.component'; 4 | 5 | const routes: Routes = [{ path: '', component: PermissionsComponent }]; 6 | 7 | @NgModule({ 8 | imports: [RouterModule.forChild(routes)], 9 | exports: [RouterModule] 10 | }) 11 | export class PermissionsRoutingModule { } 12 | -------------------------------------------------------------------------------- /src/app/pages/permissions/permissions.component.ts: -------------------------------------------------------------------------------- 1 | import { ChangeDetectionStrategy, Component } from '@angular/core'; 2 | import { PERMISSION_CODE } from '@config/permissions-code.config'; 3 | import { AuthService } from '@services/auth.service'; 4 | 5 | 6 | @Component({ 7 | selector: 'app-permissions', 8 | template: ` 9 | 10 |
11 |
12 | 19 | 26 |
27 |
28 |
29 | Angular Tshirt 30 |
31 |

Angular Tshirt

32 |

Show off that you are an Angular developer.

33 |
34 |

$25

35 |
36 |
37 |
38 |
39 | 40 | 41 | 42 |
43 |
44 |
45 |
46 | 47 |
48 | `, 49 | changeDetection: ChangeDetectionStrategy.OnPush, 50 | }) 51 | export class PermissionsComponent { 52 | code = PERMISSION_CODE; 53 | constructor(public authService: AuthService) { } 54 | } 55 | -------------------------------------------------------------------------------- /src/app/pages/permissions/permissions.module.ts: -------------------------------------------------------------------------------- 1 | import { CommonModule } from '@angular/common'; 2 | import { NgModule } from '@angular/core'; 3 | import { CodeExplorerModule } from '@components/code-explorer/code-explorer.module'; 4 | import { PageHeaderModule } from '@components/page-header/page-header.module'; 5 | import { UiPermissionsModule } from '@lib/ui-permissions/ui-permissions.module'; 6 | import { PermissionsRoutingModule } from './permissions-routing.module'; 7 | import { PermissionsComponent } from './permissions.component'; 8 | 9 | @NgModule({ 10 | declarations: [PermissionsComponent], 11 | imports: [CommonModule, PermissionsRoutingModule, PageHeaderModule, CodeExplorerModule, UiPermissionsModule], 12 | }) 13 | export class PermissionsModule {} 14 | -------------------------------------------------------------------------------- /src/app/pages/stats/stats-routing.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { RouterModule, Routes } from '@angular/router'; 3 | import { StatsComponent } from './stats.component'; 4 | 5 | const routes: Routes = [{ path: '', component: StatsComponent }]; 6 | 7 | @NgModule({ 8 | imports: [RouterModule.forChild(routes)], 9 | exports: [RouterModule] 10 | }) 11 | export class StatsRoutingModule { } 12 | -------------------------------------------------------------------------------- /src/app/pages/stats/stats.component.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | import { StatsCardData } from '@components/stats-card/stats-card.component'; 3 | import { STATS_CODE } from '@config/stats-code.config'; 4 | 5 | 6 | @Component({ 7 | selector: 'app-stats', 8 | template: ` 9 | 10 |
11 | 12 | 13 | 14 |
15 |
16 | 17 |
18 | `, 19 | styles: [ 20 | ` 21 | .stats__container { 22 | grid-template-columns: repeat(auto-fit, 300px); 23 | } 24 | `, 25 | ], 26 | }) 27 | export class StatsComponent { 28 | stats: StatsCardData[] = [ 29 | { 30 | name: 'Gross Sales', 31 | value: 1203412, 32 | delta: -5.6, 33 | }, 34 | { 35 | name: 'Total Sales', 36 | value: 4950003, 37 | delta: -9.8, 38 | }, 39 | { 40 | name: 'Expenses', 41 | value: 34520, 42 | delta: 25, 43 | }, 44 | ]; 45 | 46 | codes = STATS_CODE; 47 | } 48 | -------------------------------------------------------------------------------- /src/app/pages/stats/stats.module.ts: -------------------------------------------------------------------------------- 1 | import { CommonModule } from '@angular/common'; 2 | import { NgModule } from '@angular/core'; 3 | import { CodeExplorerModule } from '@components/code-explorer/code-explorer.module'; 4 | import { PageHeaderModule } from '@components/page-header/page-header.module'; 5 | import { StatsCardModule } from '@components/stats-card/stats-card.module'; 6 | import { UiStatsCardModule } from '@lib/ui-stats-card/ui-stats-card.module'; 7 | import { StatsRoutingModule } from './stats-routing.module'; 8 | import { StatsComponent } from './stats.component'; 9 | 10 | 11 | @NgModule({ 12 | declarations: [StatsComponent], 13 | imports: [CommonModule, StatsRoutingModule, UiStatsCardModule, PageHeaderModule, StatsCardModule, CodeExplorerModule], 14 | }) 15 | export class StatsModule {} 16 | -------------------------------------------------------------------------------- /src/app/pages/table-sort/table-sort-routing.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { RouterModule, Routes } from '@angular/router'; 3 | import { TableSortComponent } from './table-sort.component'; 4 | 5 | const routes: Routes = [{ path: '', component: TableSortComponent }]; 6 | 7 | @NgModule({ 8 | imports: [RouterModule.forChild(routes)], 9 | exports: [RouterModule] 10 | }) 11 | export class TableSortRoutingModule { } 12 | -------------------------------------------------------------------------------- /src/app/pages/table-sort/table-sort.component.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | import { TABLE_SORT_CODE } from '@config/table-sort-code.config'; 3 | 4 | @Component({ 5 | selector: 'app-table-sort', 6 | template: ` 7 | 8 |
9 | 13 |
14 |
15 | 16 |
17 | `, 18 | }) 19 | export class TableSortComponent { 20 | code = TABLE_SORT_CODE; 21 | } 22 | -------------------------------------------------------------------------------- /src/app/pages/table-sort/table-sort.module.ts: -------------------------------------------------------------------------------- 1 | import { CommonModule } from '@angular/common'; 2 | import { NgModule } from '@angular/core'; 3 | import { CodeExplorerModule } from '@components/code-explorer/code-explorer.module'; 4 | import { PageHeaderModule } from '@components/page-header/page-header.module'; 5 | import { UiTableSortModule } from '@lib/ui-table-sort/ui-table-sort.module'; 6 | import { TableSortRoutingModule } from './table-sort-routing.module'; 7 | import { TableSortComponent } from './table-sort.component'; 8 | 9 | 10 | @NgModule({ 11 | declarations: [TableSortComponent], 12 | imports: [CommonModule, TableSortRoutingModule, UiTableSortModule, CodeExplorerModule, PageHeaderModule], 13 | }) 14 | export class TableSortModule {} 15 | -------------------------------------------------------------------------------- /src/app/services/auth.service.spec.ts: -------------------------------------------------------------------------------- 1 | import { TestBed } from '@angular/core/testing'; 2 | 3 | import { AuthService } from './auth.service'; 4 | 5 | describe('AuthService', () => { 6 | let service: AuthService; 7 | 8 | beforeEach(() => { 9 | TestBed.configureTestingModule({}); 10 | service = TestBed.inject(AuthService); 11 | }); 12 | 13 | it('should be created', () => { 14 | expect(service).toBeTruthy(); 15 | }); 16 | }); 17 | -------------------------------------------------------------------------------- /src/app/services/auth.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@angular/core'; 2 | import { BehaviorSubject } from 'rxjs'; 3 | import { AUTHORIZED_USER, UNAUTHORIZED_USER } from '../config/permissions.config'; 4 | import { User } from '../interfaces/user.interface'; 5 | 6 | @Injectable({ 7 | providedIn: 'root', 8 | }) 9 | export class AuthService { 10 | loggedUser$ = new BehaviorSubject(AUTHORIZED_USER); 11 | 12 | constructor() {} 13 | unauthorizedUser() { 14 | this.loggedUser$.next(UNAUTHORIZED_USER); 15 | } 16 | authorizedUser() { 17 | this.loggedUser$.next(AUTHORIZED_USER); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/assets/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/adisreyaj/angular-directives-showcase/fe227adb724569601f8d697ec2c0982841739384/src/assets/.gitkeep -------------------------------------------------------------------------------- /src/assets/icons/maximize.svg: -------------------------------------------------------------------------------- 1 | 8 | 12 | 16 | 20 | 24 | -------------------------------------------------------------------------------- /src/assets/icons/minimize.svg: -------------------------------------------------------------------------------- 1 | 8 | 12 | 16 | -------------------------------------------------------------------------------- /src/assets/images/angular-merch.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/adisreyaj/angular-directives-showcase/fe227adb724569601f8d697ec2c0982841739384/src/assets/images/angular-merch.jpg -------------------------------------------------------------------------------- /src/assets/images/badges.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/adisreyaj/angular-directives-showcase/fe227adb724569601f8d697ec2c0982841739384/src/assets/images/badges.jpg -------------------------------------------------------------------------------- /src/assets/images/fullscreen.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/adisreyaj/angular-directives-showcase/fe227adb724569601f8d697ec2c0982841739384/src/assets/images/fullscreen.jpg -------------------------------------------------------------------------------- /src/assets/images/highlight.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/adisreyaj/angular-directives-showcase/fe227adb724569601f8d697ec2c0982841739384/src/assets/images/highlight.jpg -------------------------------------------------------------------------------- /src/assets/images/long-press.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/adisreyaj/angular-directives-showcase/fe227adb724569601f8d697ec2c0982841739384/src/assets/images/long-press.jpg -------------------------------------------------------------------------------- /src/assets/images/permissions.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/adisreyaj/angular-directives-showcase/fe227adb724569601f8d697ec2c0982841739384/src/assets/images/permissions.jpg -------------------------------------------------------------------------------- /src/assets/images/stats-card.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/adisreyaj/angular-directives-showcase/fe227adb724569601f8d697ec2c0982841739384/src/assets/images/stats-card.jpg -------------------------------------------------------------------------------- /src/assets/images/table-sort.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/adisreyaj/angular-directives-showcase/fe227adb724569601f8d697ec2c0982841739384/src/assets/images/table-sort.jpg -------------------------------------------------------------------------------- /src/assets/meta.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/adisreyaj/angular-directives-showcase/fe227adb724569601f8d697ec2c0982841739384/src/assets/meta.jpg -------------------------------------------------------------------------------- /src/environments/environment.prod.ts: -------------------------------------------------------------------------------- 1 | export const environment = { 2 | production: true 3 | }; 4 | -------------------------------------------------------------------------------- /src/environments/environment.ts: -------------------------------------------------------------------------------- 1 | // This file can be replaced during build by using the `fileReplacements` array. 2 | // `ng build` replaces `environment.ts` with `environment.prod.ts`. 3 | // The list of file replacements can be found in `angular.json`. 4 | 5 | export const environment = { 6 | production: false 7 | }; 8 | 9 | /* 10 | * For easier debugging in development mode, you can import the following file 11 | * to ignore zone related error stack frames such as `zone.run`, `zoneDelegate.invokeTask`. 12 | * 13 | * This import should be commented out in production mode because it will have a negative impact 14 | * on performance if an error is thrown. 15 | */ 16 | // import 'zone.js/plugins/zone-error'; // Included with Angular CLI. 17 | -------------------------------------------------------------------------------- /src/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/adisreyaj/angular-directives-showcase/fe227adb724569601f8d697ec2c0982841739384/src/favicon.ico -------------------------------------------------------------------------------- /src/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Angular Directives Showcase - Adithya Sreyaj 7 | 9 | 10 | 11 | 12 | 13 | 15 | 16 | 17 | 18 | 19 | 20 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | -------------------------------------------------------------------------------- /src/main.ts: -------------------------------------------------------------------------------- 1 | import { enableProdMode } from '@angular/core'; 2 | import { platformBrowserDynamic } from '@angular/platform-browser-dynamic'; 3 | 4 | import { AppModule } from './app/app.module'; 5 | import { environment } from './environments/environment'; 6 | 7 | if (environment.production) { 8 | enableProdMode(); 9 | } 10 | 11 | platformBrowserDynamic().bootstrapModule(AppModule) 12 | .catch(err => console.error(err)); 13 | -------------------------------------------------------------------------------- /src/polyfills.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * This file includes polyfills needed by Angular and is loaded before the app. 3 | * You can add your own extra polyfills to this file. 4 | * 5 | * This file is divided into 2 sections: 6 | * 1. Browser polyfills. These are applied before loading ZoneJS and are sorted by browsers. 7 | * 2. Application imports. Files imported after ZoneJS that should be loaded before your main 8 | * file. 9 | * 10 | * The current setup is for so-called "evergreen" browsers; the last versions of browsers that 11 | * automatically update themselves. This includes Safari >= 10, Chrome >= 55 (including Opera), 12 | * Edge >= 13 on the desktop, and iOS 10 and Chrome on mobile. 13 | * 14 | * Learn more in https://angular.io/guide/browser-support 15 | */ 16 | 17 | /*************************************************************************************************** 18 | * BROWSER POLYFILLS 19 | */ 20 | 21 | /** 22 | * IE11 requires the following for NgClass support on SVG elements 23 | */ 24 | // import 'classlist.js'; // Run `npm install --save classlist.js`. 25 | 26 | /** 27 | * Web Animations `@angular/platform-browser/animations` 28 | * Only required if AnimationBuilder is used within the application and using IE/Edge or Safari. 29 | * Standard animation support in Angular DOES NOT require any polyfills (as of Angular 6.0). 30 | */ 31 | // import 'web-animations-js'; // Run `npm install --save web-animations-js`. 32 | 33 | /** 34 | * By default, zone.js will patch all possible macroTask and DomEvents 35 | * user can disable parts of macroTask/DomEvents patch by setting following flags 36 | * because those flags need to be set before `zone.js` being loaded, and webpack 37 | * will put import in the top of bundle, so user need to create a separate file 38 | * in this directory (for example: zone-flags.ts), and put the following flags 39 | * into that file, and then add the following code before importing zone.js. 40 | * import './zone-flags'; 41 | * 42 | * The flags allowed in zone-flags.ts are listed here. 43 | * 44 | * The following flags will work for all browsers. 45 | * 46 | * (window as any).__Zone_disable_requestAnimationFrame = true; // disable patch requestAnimationFrame 47 | * (window as any).__Zone_disable_on_property = true; // disable patch onProperty such as onclick 48 | * (window as any).__zone_symbol__UNPATCHED_EVENTS = ['scroll', 'mousemove']; // disable patch specified eventNames 49 | * 50 | * in IE/Edge developer tools, the addEventListener will also be wrapped by zone.js 51 | * with the following flag, it will bypass `zone.js` patch for IE/Edge 52 | * 53 | * (window as any).__Zone_enable_cross_context_check = true; 54 | * 55 | */ 56 | 57 | /*************************************************************************************************** 58 | * Zone JS is required by default for Angular itself. 59 | */ 60 | import 'zone.js'; // Included with Angular CLI. 61 | 62 | 63 | /*************************************************************************************************** 64 | * APPLICATION IMPORTS 65 | */ 66 | -------------------------------------------------------------------------------- /src/styles.scss: -------------------------------------------------------------------------------- 1 | /* You can add global styles to this file, and also import other style files */ 2 | @import 'tailwindcss/base'; 3 | @import 'tailwindcss/components'; 4 | @import 'tailwindcss/utilities'; 5 | 6 | @import './app/lib/ui-badge/badge.styles.scss'; 7 | * { 8 | margin: 0; 9 | padding: 0; 10 | box-sizing: border-box; 11 | } 12 | 13 | body { 14 | font-family: 'Poppins', sans-serif; 15 | } 16 | 17 | .fullscreen { 18 | width: 100vw; 19 | height: 100vh; 20 | position: fixed; 21 | top: 0; 22 | left: 0; 23 | } 24 | 25 | pre { 26 | @apply rounded-b-md rounded-tr-md; 27 | } 28 | 29 | .btn { 30 | @apply bg-gray-100; 31 | @apply px-3 py-2; 32 | @apply rounded-md; 33 | @apply hover:bg-blue-600 hover:text-white; 34 | &.active { 35 | @apply bg-blue-600 text-white; 36 | } 37 | &.primary{ 38 | @apply bg-blue-600 text-white; 39 | @apply hover:bg-blue-800; 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/test.ts: -------------------------------------------------------------------------------- 1 | // This file is required by karma.conf.js and loads recursively all the .spec and framework files 2 | 3 | import 'zone.js/testing'; 4 | import { getTestBed } from '@angular/core/testing'; 5 | import { 6 | BrowserDynamicTestingModule, 7 | platformBrowserDynamicTesting 8 | } from '@angular/platform-browser-dynamic/testing'; 9 | 10 | declare const require: { 11 | context(path: string, deep?: boolean, filter?: RegExp): { 12 | keys(): string[]; 13 | (id: string): T; 14 | }; 15 | }; 16 | 17 | // First, initialize the Angular testing environment. 18 | getTestBed().initTestEnvironment( 19 | BrowserDynamicTestingModule, 20 | platformBrowserDynamicTesting() 21 | ); 22 | // Then we find all the tests. 23 | const context = require.context('./', true, /\.spec\.ts$/); 24 | // And load the modules. 25 | context.keys().map(context); 26 | -------------------------------------------------------------------------------- /tailwind.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | purge: ['src/app/**/*.{ts,html}'], 3 | mode: 'jit', 4 | darkMode: false, 5 | theme: { 6 | extend: {}, 7 | }, 8 | variants: { 9 | extend: {}, 10 | }, 11 | plugins: [require('@tailwindcss/forms'), require('@tailwindcss/line-clamp')], 12 | }; 13 | -------------------------------------------------------------------------------- /tsconfig.app.json: -------------------------------------------------------------------------------- 1 | /* To learn more about this file see: https://angular.io/config/tsconfig. */ 2 | { 3 | "extends": "./tsconfig.json", 4 | "compilerOptions": { 5 | "outDir": "./out-tsc/app", 6 | "types": [] 7 | }, 8 | "files": [ 9 | "src/main.ts", 10 | "src/polyfills.ts" 11 | ], 12 | "include": [ 13 | "src/**/*.d.ts" 14 | ] 15 | } 16 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | /* To learn more about this file see: https://angular.io/config/tsconfig. */ 2 | { 3 | "compileOnSave": false, 4 | "compilerOptions": { 5 | "baseUrl": "./", 6 | "outDir": "./dist/out-tsc", 7 | "forceConsistentCasingInFileNames": true, 8 | "strict": true, 9 | "noImplicitReturns": true, 10 | "noFallthroughCasesInSwitch": true, 11 | "sourceMap": true, 12 | "declaration": false, 13 | "downlevelIteration": true, 14 | "experimentalDecorators": true, 15 | "moduleResolution": "node", 16 | "importHelpers": true, 17 | "target": "es2017", 18 | "module": "es2020", 19 | "lib": [ 20 | "es2018", 21 | "dom" 22 | ], 23 | "paths": { 24 | "@lib/*": [ 25 | "src/app/lib/*" 26 | ], 27 | "@config/*": [ 28 | "src/app/config/*" 29 | ], 30 | "@components/*": [ 31 | "src/app/components/*" 32 | ], 33 | "@services/*": [ 34 | "src/app/services/*" 35 | ], 36 | "@pages/*": [ 37 | "src/app/pages/*" 38 | ], 39 | "@interfaces/*": [ 40 | "src/app/interfaces/*" 41 | ] 42 | } 43 | }, 44 | "angularCompilerOptions": { 45 | "enableI18nLegacyMessageIdFormat": false, 46 | "strictInjectionParameters": true, 47 | "strictInputAccessModifiers": true, 48 | "strictTemplates": true 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /tsconfig.spec.json: -------------------------------------------------------------------------------- 1 | /* To learn more about this file see: https://angular.io/config/tsconfig. */ 2 | { 3 | "extends": "./tsconfig.json", 4 | "compilerOptions": { 5 | "outDir": "./out-tsc/spec", 6 | "types": [ 7 | "jasmine" 8 | ] 9 | }, 10 | "files": [ 11 | "src/test.ts", 12 | "src/polyfills.ts" 13 | ], 14 | "include": [ 15 | "src/**/*.spec.ts", 16 | "src/**/*.d.ts" 17 | ] 18 | } 19 | --------------------------------------------------------------------------------