├── .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 |
4 |
5 |
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 | 
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 | 
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 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
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 | {{code.name}}
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 |
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 |
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 |
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 | Small
65 | Medium
66 | Large
67 |
68 |
69 | Test
70 | Test
71 | Test
72 | Test
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 | Press & Hold
29 | Hold me for 1s
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 | View
61 | Edit
62 | Delete
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 | First name
92 | Last name
93 | Birthday
94 |
95 |
96 |
97 |
98 | {{user?.firstname}}
99 | {{user?.lastname}}
100 | {{user?.birthday}}
101 |
102 |
103 |
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 | Small
10 | Medium
11 | Large
12 |
13 |
14 | Test
15 | Test
16 | Test
17 | Test
18 |
19 |
20 | `,
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 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
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 |
15 |
16 | Case Sensitive
17 |
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 |
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 |
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 | Press & Hold
10 | Hold me for 1s
11 |
12 |
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 |
17 | Authorized User
18 |
19 |
24 | Unauthorized User
25 |
26 |
27 |
28 |
29 |
30 |
31 |
Angular Tshirt
32 |
Show off that you are an Angular developer.
33 |
36 |
37 |
38 |
39 | View
40 | Edit
41 | Delete
42 |
43 |
44 |
45 |
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 |
15 |
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 |
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 |
--------------------------------------------------------------------------------