commit
13c5b2f943
479 changed files with 171033 additions and 0 deletions
@ -0,0 +1,17 @@ |
|||||
|
# This file is used by the build system to adjust CSS and JS output to support the specified browsers below. |
||||
|
# For additional information regarding the format and rule options, please see: |
||||
|
# https://github.com/browserslist/browserslist#queries |
||||
|
|
||||
|
# For the full list of supported browsers by the Angular framework, please see: |
||||
|
# https://angular.io/guide/browser-support |
||||
|
|
||||
|
# You can see what browsers were selected by your queries by running: |
||||
|
# npx browserslist |
||||
|
|
||||
|
last 1 Chrome version |
||||
|
last 1 Firefox version |
||||
|
last 2 Edge major versions |
||||
|
last 2 Safari major versions |
||||
|
last 2 iOS major versions |
||||
|
Firefox ESR |
||||
|
not IE 11 # Angular supports IE 11 only as an opt-in. To opt-in, remove the 'not' prefix on this line. |
@ -0,0 +1,16 @@ |
|||||
|
# Editor configuration, see https://editorconfig.org |
||||
|
root = true |
||||
|
|
||||
|
[*] |
||||
|
charset = utf-8 |
||||
|
indent_style = space |
||||
|
indent_size = 4 |
||||
|
insert_final_newline = true |
||||
|
trim_trailing_whitespace = true |
||||
|
|
||||
|
[*.ts] |
||||
|
quote_type = single |
||||
|
|
||||
|
[*.md] |
||||
|
max_line_length = off |
||||
|
trim_trailing_whitespace = false |
@ -0,0 +1,95 @@ |
|||||
|
{ |
||||
|
"root": true, |
||||
|
"env": { |
||||
|
"es6": true |
||||
|
}, |
||||
|
"parserOptions": { |
||||
|
"ecmaVersion": 2018 |
||||
|
}, |
||||
|
"ignorePatterns": [ |
||||
|
"projects/**/*" |
||||
|
], |
||||
|
"overrides": [ |
||||
|
{ |
||||
|
"files": [ |
||||
|
"*.ts" |
||||
|
], |
||||
|
"parserOptions": { |
||||
|
"project": [ |
||||
|
"tsconfig.json" |
||||
|
], |
||||
|
"createDefaultProgram": true |
||||
|
}, |
||||
|
"extends": [ |
||||
|
"plugin:@angular-eslint/ng-cli-compat", |
||||
|
"plugin:@angular-eslint/ng-cli-compat--formatting-add-on", |
||||
|
"plugin:@angular-eslint/template/process-inline-templates" |
||||
|
], |
||||
|
"rules": { |
||||
|
"@angular-eslint/component-selector": [ |
||||
|
"error", |
||||
|
{ |
||||
|
"type": "element", |
||||
|
"prefix": "", |
||||
|
"style": "kebab-case" |
||||
|
} |
||||
|
], |
||||
|
"@angular-eslint/directive-selector": [ |
||||
|
"error", |
||||
|
{ |
||||
|
"type": "attribute", |
||||
|
"prefix": "", |
||||
|
"style": "camelCase" |
||||
|
} |
||||
|
], |
||||
|
"@typescript-eslint/dot-notation": "off", |
||||
|
"@typescript-eslint/explicit-function-return-type": "error", |
||||
|
"@typescript-eslint/explicit-member-accessibility": [ |
||||
|
"off", |
||||
|
{ |
||||
|
"accessibility": "explicit" |
||||
|
} |
||||
|
], |
||||
|
"@typescript-eslint/no-inferrable-types": "off", |
||||
|
"arrow-parens": [ |
||||
|
"error", |
||||
|
"as-needed", |
||||
|
{ |
||||
|
"requireForBlockBody": true |
||||
|
} |
||||
|
], |
||||
|
"brace-style": [ |
||||
|
"off", |
||||
|
"off" |
||||
|
], |
||||
|
"import/order": "off", |
||||
|
"max-len": [ |
||||
|
"error", |
||||
|
{ |
||||
|
"ignorePattern": "^import |^export | implements", |
||||
|
"code": 180 |
||||
|
} |
||||
|
], |
||||
|
"no-underscore-dangle": "off", |
||||
|
"object-shorthand": "off", |
||||
|
"quote-props": [ |
||||
|
"error", |
||||
|
"consistent" |
||||
|
], |
||||
|
"quotes": [ |
||||
|
"error", |
||||
|
"single" |
||||
|
] |
||||
|
} |
||||
|
}, |
||||
|
{ |
||||
|
"files": [ |
||||
|
"*.html" |
||||
|
], |
||||
|
"extends": [ |
||||
|
"plugin:@angular-eslint/template/recommended" |
||||
|
], |
||||
|
"rules": {} |
||||
|
} |
||||
|
] |
||||
|
} |
@ -0,0 +1,48 @@ |
|||||
|
# See http://help.github.com/ignore-files/ for more about ignoring files. |
||||
|
|
||||
|
# compiled output |
||||
|
/dist |
||||
|
/tmp |
||||
|
/out-tsc |
||||
|
# Only exists if Bazel was run |
||||
|
/bazel-out |
||||
|
|
||||
|
#angular |
||||
|
/.angular |
||||
|
|
||||
|
# dependencies |
||||
|
/node_modules |
||||
|
|
||||
|
# profiling files |
||||
|
chrome-profiler-events*.json |
||||
|
|
||||
|
# IDEs and editors |
||||
|
/.idea |
||||
|
.project |
||||
|
.classpath |
||||
|
.c9/ |
||||
|
*.launch |
||||
|
.settings/ |
||||
|
*.sublime-workspace |
||||
|
|
||||
|
# IDE - VSCode |
||||
|
.vscode/* |
||||
|
!.vscode/settings.json |
||||
|
!.vscode/tasks.json |
||||
|
!.vscode/launch.json |
||||
|
!.vscode/extensions.json |
||||
|
.history/* |
||||
|
|
||||
|
# misc |
||||
|
/.sass-cache |
||||
|
/connect.lock |
||||
|
/coverage |
||||
|
/libpeerconnection.log |
||||
|
npm-debug.log |
||||
|
yarn-error.log |
||||
|
testem.log |
||||
|
/typings |
||||
|
|
||||
|
# System Files |
||||
|
.DS_Store |
||||
|
Thumbs.db |
@ -0,0 +1 @@ |
|||||
|
12 |
@ -0,0 +1,72 @@ |
|||||
|
// ----------------------------------------------------------------------------------------------------- |
||||
|
// @ 3rd party credits |
||||
|
// ----------------------------------------------------------------------------------------------------- |
||||
|
|
||||
|
// Flags |
||||
|
https://github.com/Yummygum/flagpack-core |
||||
|
|
||||
|
// Icons |
||||
|
Material - https://material.io/tools/icons |
||||
|
Feather - https://feathericons.com/ |
||||
|
Heroicons - https://github.com/refactoringui/heroicons |
||||
|
Iconsmind - https://iconsmind.com/ |
||||
|
|
||||
|
// Avatars |
||||
|
https://uifaces.co |
||||
|
|
||||
|
// 404, 500 & Maintenance |
||||
|
https://undraw.co |
||||
|
|
||||
|
// Mail app |
||||
|
Photo by Riccardo Chiarini on Unsplash - https://unsplash.com/photos/2VDa8bnLM8c |
||||
|
Photo by Johannes Plenio on Unsplash - https://unsplash.com/photos/RwHv7LgeC7s |
||||
|
Photo by Jamie Davies on Unsplash - https://unsplash.com/photos/Hao52Fu9-F8 |
||||
|
Photo by Christian Joudrey on Unsplash - https://unsplash.com/photos/mWRR1xj95hg |
||||
|
|
||||
|
// Profile page |
||||
|
Photo by Alex Knight on Unsplash - https://unsplash.com/photos/DpPutJwgyW8 |
||||
|
|
||||
|
// Cards |
||||
|
Photo by Kym Ellis on Unsplash - https://unsplash.com/photos/RPT3AjdXlZc |
||||
|
Photo by Patrick Hendry on Unsplash - https://unsplash.com/photos/Qgxk3PQsMiI |
||||
|
Photo by Hailey Kean on Unsplash - https://unsplash.com/photos/QxjsOlFNr_4 |
||||
|
Photo by Nathan Anderson on Unsplash - https://unsplash.com/photos/mG8ShlWrMDI |
||||
|
Photo by Adrian Infernus on Unsplash - https://unsplash.com/photos/5apewqWk978 |
||||
|
Photo by freestocks.org on Unsplash - https://unsplash.com/photos/c73TZ2sIU38 |
||||
|
Photo by Tim Marshall on Unsplash - https://unsplash.com/photos/PKSCrmZdvwA |
||||
|
Photo by Daniel Koponyas on Unsplash - https://unsplash.com/photos/rbiLY6ZwvXQ |
||||
|
Photo by John Westrock on Unsplash - https://unsplash.com/photos/LCesauDseu8 |
||||
|
Photo by Gabriel Sollmann on Unsplash - https://unsplash.com/photos/kFWj9y-tJB4 |
||||
|
Photo by Kevin Wolf on Unsplash - https://unsplash.com/photos/BJyjgEdNTPs |
||||
|
Photo by Luca Bravo on Unsplash - https://unsplash.com/photos/hFzIoD0F_i8 |
||||
|
Photo by Ian Baldwin on Unsplash - https://unsplash.com/photos/Dlj-SxxTlQ0 |
||||
|
Photo by Ben Kolde on Unsplash - https://unsplash.com/photos/KRTFIBOfcFw |
||||
|
Photo by Chad Peltola on Unsplash - https://unsplash.com/photos/BTvQ2ET_iKc |
||||
|
Photo by rocknwool on Unsplash - https://unsplash.com/photos/r56oO1V5oms |
||||
|
Photo by Vita Vilcina on Unsplash - https://unsplash.com/photos/KtOid0FLjqU |
||||
|
Photo by Jia Ye on Unsplash - https://unsplash.com/photos/y8ZnQqgohLk |
||||
|
Photo by Parker Whitson on Unsplash - https://unsplash.com/photos/OlTYIqTjmVM |
||||
|
Photo by Dorian Hurst on Unsplash - https://unsplash.com/photos/a9uWPQlIbYc |
||||
|
Photo by Everaldo Coelho on Unsplash - https://unsplash.com/photos/KPaSCpklCZw |
||||
|
Photo by eberhard grossgasteiger on Unsplash - https://unsplash.com/photos/fh2JefbNlII |
||||
|
Photo by Orlova Maria on Unsplash - https://unsplash.com/photos/p8y4dWEMGMU |
||||
|
Photo by Jake Blucker on Unsplash - https://unsplash.com/photos/tMzCrBkM99Y |
||||
|
Photo by Jerry Zhang on Unsplash - https://unsplash.com/photos/oIBcow6n36s |
||||
|
Photo by John Cobb on Unsplash - https://unsplash.com/photos/IE_sifhay7o |
||||
|
Photo by Dan Gold on Unsplash - https://unsplash.com/photos/mDlhOIfGxNI |
||||
|
Photo by Ana Toma on Unsplash - https://unsplash.com/photos/XsGwe6gYg0c |
||||
|
Photo by Andrea on Unsplash - https://unsplash.com/photos/1AWY0N960Sk |
||||
|
Photo by Aswin on Unsplash - https://unsplash.com/photos/_roUcFWstas |
||||
|
Photo by Justin Kauffman on Unsplash - https://unsplash.com/photos/aWG_dqyhI0A |
||||
|
Photo by Barna Bartis on Unsplash - https://unsplash.com/photos/VVoBQqWrvkc |
||||
|
Photo by Kyle Hinkson on Unsplash - https://unsplash.com/photos/3439EnvnAGo |
||||
|
Photo by Spencer Watson on Unsplash - https://unsplash.com/photos/5TBf16GnHKg |
||||
|
Photo by adrian on Unsplash - https://unsplash.com/photos/1wrzvwoK8A4 |
||||
|
Photo by Christopher Rusev on Unsplash - https://unsplash.com/photos/7gKWgCRixf0 |
||||
|
Photo by Stephen Leonardi on Unsplash - https://unsplash.com/photos/MDmwQVgDHHM |
||||
|
Photo by Dwinanda Nurhanif Mujito on Unsplash - https://unsplash.com/photos/pKT5Mg16w_w |
||||
|
Photo by Humphrey Muleba on Unsplash - https://unsplash.com/photos/Zuvf5mxT5fs |
||||
|
Photo by adrian on Unsplash - https://unsplash.com/photos/PNRxLFPMyJY |
||||
|
Photo by Dahee Son on Unsplash - https://unsplash.com/photos/tV06QVJXVxU |
||||
|
Photo by Zachary Kyra-Derksen on Unsplash - https://unsplash.com/photos/vkqS7vLQUtg |
||||
|
Photo by Rodrigo Soares on Unsplash - https://unsplash.com/photos/8BFWBUkSqQo |
@ -0,0 +1,6 @@ |
|||||
|
Envato Standard License |
||||
|
|
||||
|
Copyright (c) Sercan Yemen <sercanyemen@gmail.com> |
||||
|
|
||||
|
This project is protected by Envato's Standard License. For more information, |
||||
|
check the official license page at [https://themeforest.net/licenses/standard](https://themeforest.net/licenses/standard) |
@ -0,0 +1,27 @@ |
|||||
|
# Fuse - Admin template and Starter project for Angular |
||||
|
|
||||
|
This project was generated with [Angular CLI](https://github.com/angular/angular-cli) |
||||
|
|
||||
|
## Development server |
||||
|
|
||||
|
Run `ng serve` for a dev server. Navigate to `http://localhost:4200/`. The app will automatically reload if you change any of the source files. |
||||
|
|
||||
|
## Code scaffolding |
||||
|
|
||||
|
Run `ng generate component component-name` to generate a new component. You can also use `ng generate directive|pipe|service|class|guard|interface|enum|module`. |
||||
|
|
||||
|
## Build |
||||
|
|
||||
|
Run `ng build` to build the project. The build artifacts will be stored in the `dist/` directory. |
||||
|
|
||||
|
## Running unit tests |
||||
|
|
||||
|
Run `ng test` to execute the unit tests via [Karma](https://karma-runner.github.io). |
||||
|
|
||||
|
## Running end-to-end tests |
||||
|
|
||||
|
Run `ng e2e` to execute the end-to-end tests via a platform of your choice. To use this command, you need to first add a package that implements end-to-end testing capabilities. |
||||
|
|
||||
|
## Further help |
||||
|
|
||||
|
To get more help on the Angular CLI use `ng help` or go check out the [Angular CLI Overview and Command Reference](https://angular.io/cli) page. |
@ -0,0 +1,141 @@ |
|||||
|
{ |
||||
|
"$schema": "./node_modules/@angular/cli/lib/config/schema.json", |
||||
|
"version": 1, |
||||
|
"newProjectRoot": "projects", |
||||
|
"projects": { |
||||
|
"fuse": { |
||||
|
"projectType": "application", |
||||
|
"schematics": { |
||||
|
"@schematics/angular:component": { |
||||
|
"style": "scss" |
||||
|
} |
||||
|
}, |
||||
|
"root": "", |
||||
|
"sourceRoot": "src", |
||||
|
"prefix": "app", |
||||
|
"architect": { |
||||
|
"build": { |
||||
|
"builder": "@angular-devkit/build-angular:browser", |
||||
|
"options": { |
||||
|
"outputPath": "dist/fuse", |
||||
|
"index": "src/index.html", |
||||
|
"main": "src/main.ts", |
||||
|
"polyfills": "src/polyfills.ts", |
||||
|
"tsConfig": "tsconfig.app.json", |
||||
|
"inlineStyleLanguage": "scss", |
||||
|
"allowedCommonJsDependencies": [ |
||||
|
"apexcharts", |
||||
|
"highlight.js", |
||||
|
"crypto-js/enc-utf8", |
||||
|
"crypto-js/hmac-sha256", |
||||
|
"crypto-js/enc-base64" |
||||
|
], |
||||
|
"assets": [ |
||||
|
"src/favicon-16x16.png", |
||||
|
"src/favicon-32x32.png", |
||||
|
"src/assets", |
||||
|
{ |
||||
|
"glob": "_redirects", |
||||
|
"input": "src", |
||||
|
"output": "/" |
||||
|
} |
||||
|
], |
||||
|
"stylePreprocessorOptions": { |
||||
|
"includePaths": [ |
||||
|
"src/@fuse/styles" |
||||
|
] |
||||
|
}, |
||||
|
"styles": [ |
||||
|
"src/@fuse/styles/tailwind.scss", |
||||
|
"src/@fuse/styles/themes.scss", |
||||
|
"src/styles/vendors.scss", |
||||
|
"src/@fuse/styles/main.scss", |
||||
|
"src/styles/styles.scss", |
||||
|
"src/styles/tailwind.scss" |
||||
|
], |
||||
|
"scripts": [] |
||||
|
}, |
||||
|
"configurations": { |
||||
|
"production": { |
||||
|
"budgets": [{ |
||||
|
"type": "initial", |
||||
|
"maximumWarning": "3mb", |
||||
|
"maximumError": "5mb" |
||||
|
}, |
||||
|
{ |
||||
|
"type": "anyComponentStyle", |
||||
|
"maximumWarning": "75kb", |
||||
|
"maximumError": "90kb" |
||||
|
} |
||||
|
], |
||||
|
"fileReplacements": [{ |
||||
|
"replace": "src/environments/environment.ts", |
||||
|
"with": "src/environments/environment.prod.ts" |
||||
|
}], |
||||
|
"outputHashing": "all" |
||||
|
}, |
||||
|
"development": { |
||||
|
"buildOptimizer": false, |
||||
|
"optimization": false, |
||||
|
"vendorChunk": true, |
||||
|
"extractLicenses": false, |
||||
|
"sourceMap": true, |
||||
|
"namedChunks": true |
||||
|
} |
||||
|
}, |
||||
|
"defaultConfiguration": "production" |
||||
|
}, |
||||
|
"serve": { |
||||
|
"builder": "@angular-devkit/build-angular:dev-server", |
||||
|
"configurations": { |
||||
|
"production": { |
||||
|
"browserTarget": "fuse:build:production" |
||||
|
}, |
||||
|
"development": { |
||||
|
"browserTarget": "fuse:build:development" |
||||
|
} |
||||
|
}, |
||||
|
"defaultConfiguration": "development" |
||||
|
}, |
||||
|
"extract-i18n": { |
||||
|
"builder": "@angular-devkit/build-angular:extract-i18n", |
||||
|
"options": { |
||||
|
"browserTarget": "fuse:build" |
||||
|
} |
||||
|
}, |
||||
|
"test": { |
||||
|
"builder": "@angular-devkit/build-angular:karma", |
||||
|
"options": { |
||||
|
"main": "src/test.ts", |
||||
|
"polyfills": "src/polyfills.ts", |
||||
|
"tsConfig": "tsconfig.spec.json", |
||||
|
"karmaConfig": "karma.conf.js", |
||||
|
"inlineStyleLanguage": "scss", |
||||
|
"assets": [ |
||||
|
"src/favicon-16x16.png", |
||||
|
"src/favicon-32x32.png", |
||||
|
"src/assets" |
||||
|
], |
||||
|
"styles": [ |
||||
|
"src/styles/styles.scss" |
||||
|
], |
||||
|
"scripts": [] |
||||
|
} |
||||
|
}, |
||||
|
"lint": { |
||||
|
"builder": "@angular-eslint/builder:lint", |
||||
|
"options": { |
||||
|
"lintFilePatterns": [ |
||||
|
"src/**/*.ts", |
||||
|
"src/**/*.html" |
||||
|
] |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
}, |
||||
|
"defaultProject": "Teso", |
||||
|
"cli": { |
||||
|
"defaultCollection": "@angular-eslint/schematics" |
||||
|
} |
||||
|
} |
@ -0,0 +1,45 @@ |
|||||
|
// Karma configuration file, see link for more information
|
||||
|
// https://karma-runner.github.io/1.0/config/configuration-file.html
|
||||
|
|
||||
|
module.exports = function (config) |
||||
|
{ |
||||
|
config.set({ |
||||
|
basePath : '', |
||||
|
frameworks : ['jasmine', '@angular-devkit/build-angular'], |
||||
|
plugins : [ |
||||
|
require('karma-jasmine'), |
||||
|
require('karma-chrome-launcher'), |
||||
|
require('karma-jasmine-html-reporter'), |
||||
|
require('karma-coverage'), |
||||
|
require('@angular-devkit/build-angular/plugins/karma') |
||||
|
], |
||||
|
client : { |
||||
|
jasmine : { |
||||
|
// you can add configuration options for Jasmine here
|
||||
|
// the possible options are listed at https://jasmine.github.io/api/edge/Configuration.html
|
||||
|
// for example, you can disable the random execution with `random: false`
|
||||
|
// or set a specific seed with `seed: 4321`
|
||||
|
}, |
||||
|
clearContext: false // leave Jasmine Spec Runner output visible in browser
|
||||
|
}, |
||||
|
jasmineHtmlReporter: { |
||||
|
suppressAll: true // removes the duplicated traces
|
||||
|
}, |
||||
|
coverageReporter : { |
||||
|
dir : require('path').join(__dirname, './coverage/fuse'), |
||||
|
subdir : '.', |
||||
|
reporters: [ |
||||
|
{type: 'html'}, |
||||
|
{type: 'text-summary'} |
||||
|
] |
||||
|
}, |
||||
|
reporters : ['progress', 'kjhtml'], |
||||
|
port : 9876, |
||||
|
colors : true, |
||||
|
logLevel : config.LOG_INFO, |
||||
|
autoWatch : true, |
||||
|
browsers : ['Chrome'], |
||||
|
singleRun : false, |
||||
|
restartOnFileChange: true |
||||
|
}); |
||||
|
}; |
File diff suppressed because it is too large
@ -0,0 +1,96 @@ |
|||||
|
{ |
||||
|
"name": "teso", |
||||
|
"version": "13.6.2", |
||||
|
"description": "Teso Web portal", |
||||
|
"private": true, |
||||
|
"scripts": { |
||||
|
"ng": "ng", |
||||
|
"start": "ng serve", |
||||
|
"build": "ng build", |
||||
|
"watch": "ng build --watch --configuration development", |
||||
|
"test": "ng test", |
||||
|
"lint": "ng lint" |
||||
|
}, |
||||
|
"dependencies": { |
||||
|
"-": "^0.0.1", |
||||
|
"@angular/animations": "13.0.2", |
||||
|
"@angular/cdk": "13.0.2", |
||||
|
"@angular/common": "13.0.2", |
||||
|
"@angular/compiler": "13.0.2", |
||||
|
"@angular/core": "13.0.2", |
||||
|
"@angular/forms": "13.0.2", |
||||
|
"@angular/material": "13.0.2", |
||||
|
"@angular/material-moment-adapter": "13.0.2", |
||||
|
"@angular/platform-browser": "13.0.2", |
||||
|
"@angular/platform-browser-dynamic": "13.0.2", |
||||
|
"@angular/router": "13.0.2", |
||||
|
"@fullcalendar/angular": "4.4.5-beta", |
||||
|
"@fullcalendar/core": "4.4.2", |
||||
|
"@fullcalendar/daygrid": "4.4.2", |
||||
|
"@fullcalendar/interaction": "4.4.2", |
||||
|
"@fullcalendar/list": "4.4.2", |
||||
|
"@fullcalendar/moment": "4.4.2", |
||||
|
"@fullcalendar/rrule": "4.4.2", |
||||
|
"@fullcalendar/timegrid": "4.4.2", |
||||
|
"@ngneat/transloco": "^2.23.5", |
||||
|
"apexcharts": "3.28.1", |
||||
|
"crypto-js": "3.3.0", |
||||
|
"dayjs": "^1.10.7", |
||||
|
"highlight.js": "11.2.0", |
||||
|
"lodash-es": "4.17.21", |
||||
|
"moment": "2.29.1", |
||||
|
"ng-apexcharts": "1.5.12", |
||||
|
"ngx-markdown": "12.0.1", |
||||
|
"ngx-quill": "14.3.0", |
||||
|
"node.js": "^0.0.1-security", |
||||
|
"perfect-scrollbar": "1.5.2", |
||||
|
"quill": "1.3.7", |
||||
|
"rrule": "2.6.8", |
||||
|
"rxjs": "6.6.7", |
||||
|
"source": "^0.0.3", |
||||
|
"source-map": "^0.7.3", |
||||
|
"sourcemap-codec": "^1.4.8", |
||||
|
"support": "^1.1.4", |
||||
|
"tslib": "2.3.1", |
||||
|
"web-animations-js": "2.3.2", |
||||
|
"zone.js": "0.11.4" |
||||
|
}, |
||||
|
"devDependencies": { |
||||
|
"@angular-devkit/build-angular": "^13.0.3", |
||||
|
"@angular-eslint/builder": "^12.7.0", |
||||
|
"@angular-eslint/eslint-plugin": "12.3.1", |
||||
|
"@angular-eslint/eslint-plugin-template": "12.3.1", |
||||
|
"@angular-eslint/schematics": "^13.0.1", |
||||
|
"@angular-eslint/template-parser": "12.3.1", |
||||
|
"@angular/cli": "^13.0.3", |
||||
|
"@angular/compiler-cli": "13.0.2", |
||||
|
"@tailwindcss/aspect-ratio": "0.2.1", |
||||
|
"@tailwindcss/line-clamp": "0.2.1", |
||||
|
"@tailwindcss/typography": "0.4.1", |
||||
|
"@types/chroma-js": "2.1.3", |
||||
|
"@types/crypto-js": "3.1.47", |
||||
|
"@types/highlight.js": "10.1.0", |
||||
|
"@types/jasmine": "3.8.2", |
||||
|
"@types/lodash": "4.14.172", |
||||
|
"@types/lodash-es": "4.17.4", |
||||
|
"@types/node": "12.20.21", |
||||
|
"@typescript-eslint/eslint-plugin": "4.30.0", |
||||
|
"@typescript-eslint/parser": "4.30.0", |
||||
|
"autoprefixer": "10.3.3", |
||||
|
"chroma-js": "2.1.2", |
||||
|
"eslint": "7.32.0", |
||||
|
"eslint-plugin-import": "2.24.2", |
||||
|
"eslint-plugin-jsdoc": "36.0.8", |
||||
|
"eslint-plugin-prefer-arrow": "1.2.3", |
||||
|
"jasmine-core": "3.8.0", |
||||
|
"karma": "6.3.4", |
||||
|
"karma-chrome-launcher": "3.1.0", |
||||
|
"karma-coverage": "2.0.3", |
||||
|
"karma-jasmine": "4.0.1", |
||||
|
"karma-jasmine-html-reporter": "1.7.0", |
||||
|
"lodash": "4.17.21", |
||||
|
"postcss": "8.3.6", |
||||
|
"tailwindcss": "2.2.9", |
||||
|
"typescript": "4.4.4" |
||||
|
} |
||||
|
} |
@ -0,0 +1,14 @@ |
|||||
|
export class FuseAnimationCurves |
||||
|
{ |
||||
|
static standard = 'cubic-bezier(0.4, 0.0, 0.2, 1)'; |
||||
|
static deceleration = 'cubic-bezier(0.0, 0.0, 0.2, 1)'; |
||||
|
static acceleration = 'cubic-bezier(0.4, 0.0, 1, 1)'; |
||||
|
static sharp = 'cubic-bezier(0.4, 0.0, 0.6, 1)'; |
||||
|
} |
||||
|
|
||||
|
export class FuseAnimationDurations |
||||
|
{ |
||||
|
static complex = '375ms'; |
||||
|
static entering = '225ms'; |
||||
|
static exiting = '195ms'; |
||||
|
} |
@ -0,0 +1,34 @@ |
|||||
|
import { animate, state, style, transition, trigger } from '@angular/animations'; |
||||
|
import { FuseAnimationCurves, FuseAnimationDurations } from '@fuse/animations/defaults'; |
||||
|
|
||||
|
// -----------------------------------------------------------------------------------------------------
|
||||
|
// @ Expand / collapse
|
||||
|
// -----------------------------------------------------------------------------------------------------
|
||||
|
const expandCollapse = trigger('expandCollapse', |
||||
|
[ |
||||
|
state('void, collapsed', |
||||
|
style({ |
||||
|
height: '0' |
||||
|
}) |
||||
|
), |
||||
|
|
||||
|
state('*, expanded', |
||||
|
style('*') |
||||
|
), |
||||
|
|
||||
|
// Prevent the transition if the state is false
|
||||
|
transition('void <=> false, collapsed <=> false, expanded <=> false', []), |
||||
|
|
||||
|
// Transition
|
||||
|
transition('void <=> *, collapsed <=> expanded', |
||||
|
animate('{{timings}}'), |
||||
|
{ |
||||
|
params: { |
||||
|
timings: `${FuseAnimationDurations.entering} ${FuseAnimationCurves.deceleration}` |
||||
|
} |
||||
|
} |
||||
|
) |
||||
|
] |
||||
|
); |
||||
|
|
||||
|
export { expandCollapse }; |
@ -0,0 +1,330 @@ |
|||||
|
import { animate, state, style, transition, trigger } from '@angular/animations'; |
||||
|
import { FuseAnimationCurves, FuseAnimationDurations } from '@fuse/animations/defaults'; |
||||
|
|
||||
|
// -----------------------------------------------------------------------------------------------------
|
||||
|
// @ Fade in
|
||||
|
// -----------------------------------------------------------------------------------------------------
|
||||
|
const fadeIn = trigger('fadeIn', |
||||
|
[ |
||||
|
state('void', |
||||
|
style({ |
||||
|
opacity: 0 |
||||
|
}) |
||||
|
), |
||||
|
|
||||
|
state('*', |
||||
|
style({ |
||||
|
opacity: 1 |
||||
|
}) |
||||
|
), |
||||
|
|
||||
|
// Prevent the transition if the state is false
|
||||
|
transition('void => false', []), |
||||
|
|
||||
|
// Transition
|
||||
|
transition('void => *', animate('{{timings}}'), |
||||
|
{ |
||||
|
params: { |
||||
|
timings: `${FuseAnimationDurations.entering} ${FuseAnimationCurves.deceleration}` |
||||
|
} |
||||
|
} |
||||
|
) |
||||
|
] |
||||
|
); |
||||
|
|
||||
|
// -----------------------------------------------------------------------------------------------------
|
||||
|
// @ Fade in top
|
||||
|
// -----------------------------------------------------------------------------------------------------
|
||||
|
const fadeInTop = trigger('fadeInTop', |
||||
|
[ |
||||
|
state('void', |
||||
|
style({ |
||||
|
opacity : 0, |
||||
|
transform: 'translate3d(0, -100%, 0)' |
||||
|
}) |
||||
|
), |
||||
|
|
||||
|
state('*', |
||||
|
style({ |
||||
|
opacity : 1, |
||||
|
transform: 'translate3d(0, 0, 0)' |
||||
|
}) |
||||
|
), |
||||
|
|
||||
|
// Prevent the transition if the state is false
|
||||
|
transition('void => false', []), |
||||
|
|
||||
|
// Transition
|
||||
|
transition('void => *', animate('{{timings}}'), |
||||
|
{ |
||||
|
params: { |
||||
|
timings: `${FuseAnimationDurations.entering} ${FuseAnimationCurves.deceleration}` |
||||
|
} |
||||
|
} |
||||
|
) |
||||
|
] |
||||
|
); |
||||
|
|
||||
|
// -----------------------------------------------------------------------------------------------------
|
||||
|
// @ Fade in bottom
|
||||
|
// -----------------------------------------------------------------------------------------------------
|
||||
|
const fadeInBottom = trigger('fadeInBottom', |
||||
|
[ |
||||
|
state('void', |
||||
|
style({ |
||||
|
opacity : 0, |
||||
|
transform: 'translate3d(0, 100%, 0)' |
||||
|
}) |
||||
|
), |
||||
|
|
||||
|
state('*', |
||||
|
style({ |
||||
|
opacity : 1, |
||||
|
transform: 'translate3d(0, 0, 0)' |
||||
|
}) |
||||
|
), |
||||
|
|
||||
|
// Prevent the transition if the state is false
|
||||
|
transition('void => false', []), |
||||
|
|
||||
|
// Transition
|
||||
|
transition('void => *', animate('{{timings}}'), |
||||
|
{ |
||||
|
params: { |
||||
|
timings: `${FuseAnimationDurations.entering} ${FuseAnimationCurves.deceleration}` |
||||
|
} |
||||
|
} |
||||
|
) |
||||
|
] |
||||
|
); |
||||
|
|
||||
|
// -----------------------------------------------------------------------------------------------------
|
||||
|
// @ Fade in left
|
||||
|
// -----------------------------------------------------------------------------------------------------
|
||||
|
const fadeInLeft = trigger('fadeInLeft', |
||||
|
[ |
||||
|
state('void', |
||||
|
style({ |
||||
|
opacity : 0, |
||||
|
transform: 'translate3d(-100%, 0, 0)' |
||||
|
}) |
||||
|
), |
||||
|
|
||||
|
state('*', |
||||
|
style({ |
||||
|
opacity : 1, |
||||
|
transform: 'translate3d(0, 0, 0)' |
||||
|
}) |
||||
|
), |
||||
|
|
||||
|
// Prevent the transition if the state is false
|
||||
|
transition('void => false', []), |
||||
|
|
||||
|
// Transition
|
||||
|
transition('void => *', animate('{{timings}}'), |
||||
|
{ |
||||
|
params: { |
||||
|
timings: `${FuseAnimationDurations.entering} ${FuseAnimationCurves.deceleration}` |
||||
|
} |
||||
|
} |
||||
|
) |
||||
|
] |
||||
|
); |
||||
|
|
||||
|
// -----------------------------------------------------------------------------------------------------
|
||||
|
// @ Fade in right
|
||||
|
// -----------------------------------------------------------------------------------------------------
|
||||
|
const fadeInRight = trigger('fadeInRight', |
||||
|
[ |
||||
|
state('void', |
||||
|
style({ |
||||
|
opacity : 0, |
||||
|
transform: 'translate3d(100%, 0, 0)' |
||||
|
}) |
||||
|
), |
||||
|
|
||||
|
state('*', |
||||
|
style({ |
||||
|
opacity : 1, |
||||
|
transform: 'translate3d(0, 0, 0)' |
||||
|
}) |
||||
|
), |
||||
|
|
||||
|
// Prevent the transition if the state is false
|
||||
|
transition('void => false', []), |
||||
|
|
||||
|
// Transition
|
||||
|
transition('void => *', animate('{{timings}}'), |
||||
|
{ |
||||
|
params: { |
||||
|
timings: `${FuseAnimationDurations.entering} ${FuseAnimationCurves.deceleration}` |
||||
|
} |
||||
|
} |
||||
|
) |
||||
|
] |
||||
|
); |
||||
|
|
||||
|
// -----------------------------------------------------------------------------------------------------
|
||||
|
// @ Fade out
|
||||
|
// -----------------------------------------------------------------------------------------------------
|
||||
|
const fadeOut = trigger('fadeOut', |
||||
|
[ |
||||
|
state('*', |
||||
|
style({ |
||||
|
opacity: 1 |
||||
|
}) |
||||
|
), |
||||
|
|
||||
|
state('void', |
||||
|
style({ |
||||
|
opacity: 0 |
||||
|
}) |
||||
|
), |
||||
|
|
||||
|
// Prevent the transition if the state is false
|
||||
|
transition('false => void', []), |
||||
|
|
||||
|
// Transition
|
||||
|
transition('* => void', animate('{{timings}}'), |
||||
|
{ |
||||
|
params: { |
||||
|
timings: `${FuseAnimationDurations.exiting} ${FuseAnimationCurves.acceleration}` |
||||
|
} |
||||
|
} |
||||
|
) |
||||
|
] |
||||
|
); |
||||
|
|
||||
|
// -----------------------------------------------------------------------------------------------------
|
||||
|
// @ Fade out top
|
||||
|
// -----------------------------------------------------------------------------------------------------
|
||||
|
const fadeOutTop = trigger('fadeOutTop', |
||||
|
[ |
||||
|
state('*', |
||||
|
style({ |
||||
|
opacity : 1, |
||||
|
transform: 'translate3d(0, 0, 0)' |
||||
|
}) |
||||
|
), |
||||
|
|
||||
|
state('void', |
||||
|
style({ |
||||
|
opacity : 0, |
||||
|
transform: 'translate3d(0, -100%, 0)' |
||||
|
}) |
||||
|
), |
||||
|
|
||||
|
// Prevent the transition if the state is false
|
||||
|
transition('false => void', []), |
||||
|
|
||||
|
// Transition
|
||||
|
transition('* => void', animate('{{timings}}'), |
||||
|
{ |
||||
|
params: { |
||||
|
timings: `${FuseAnimationDurations.exiting} ${FuseAnimationCurves.acceleration}` |
||||
|
} |
||||
|
} |
||||
|
) |
||||
|
] |
||||
|
); |
||||
|
|
||||
|
// -----------------------------------------------------------------------------------------------------
|
||||
|
// @ Fade out bottom
|
||||
|
// -----------------------------------------------------------------------------------------------------
|
||||
|
const fadeOutBottom = trigger('fadeOutBottom', |
||||
|
[ |
||||
|
state('*', |
||||
|
style({ |
||||
|
opacity : 1, |
||||
|
transform: 'translate3d(0, 0, 0)' |
||||
|
}) |
||||
|
), |
||||
|
|
||||
|
state('void', |
||||
|
style({ |
||||
|
opacity : 0, |
||||
|
transform: 'translate3d(0, 100%, 0)' |
||||
|
}) |
||||
|
), |
||||
|
|
||||
|
// Prevent the transition if the state is false
|
||||
|
transition('false => void', []), |
||||
|
|
||||
|
// Transition
|
||||
|
transition('* => void', animate('{{timings}}'), |
||||
|
{ |
||||
|
params: { |
||||
|
timings: `${FuseAnimationDurations.exiting} ${FuseAnimationCurves.acceleration}` |
||||
|
} |
||||
|
} |
||||
|
) |
||||
|
] |
||||
|
); |
||||
|
|
||||
|
// -----------------------------------------------------------------------------------------------------
|
||||
|
// @ Fade out left
|
||||
|
// -----------------------------------------------------------------------------------------------------
|
||||
|
const fadeOutLeft = trigger('fadeOutLeft', |
||||
|
[ |
||||
|
state('*', |
||||
|
style({ |
||||
|
opacity : 1, |
||||
|
transform: 'translate3d(0, 0, 0)' |
||||
|
}) |
||||
|
), |
||||
|
|
||||
|
state('void', |
||||
|
style({ |
||||
|
opacity : 0, |
||||
|
transform: 'translate3d(-100%, 0, 0)' |
||||
|
}) |
||||
|
), |
||||
|
|
||||
|
// Prevent the transition if the state is false
|
||||
|
transition('false => void', []), |
||||
|
|
||||
|
// Transition
|
||||
|
transition('* => void', animate('{{timings}}'), |
||||
|
{ |
||||
|
params: { |
||||
|
timings: `${FuseAnimationDurations.exiting} ${FuseAnimationCurves.acceleration}` |
||||
|
} |
||||
|
} |
||||
|
) |
||||
|
] |
||||
|
); |
||||
|
|
||||
|
// -----------------------------------------------------------------------------------------------------
|
||||
|
// @ Fade out right
|
||||
|
// -----------------------------------------------------------------------------------------------------
|
||||
|
const fadeOutRight = trigger('fadeOutRight', |
||||
|
[ |
||||
|
state('*', |
||||
|
style({ |
||||
|
opacity : 1, |
||||
|
transform: 'translate3d(0, 0, 0)' |
||||
|
}) |
||||
|
), |
||||
|
|
||||
|
state('void', |
||||
|
style({ |
||||
|
opacity : 0, |
||||
|
transform: 'translate3d(100%, 0, 0)' |
||||
|
}) |
||||
|
), |
||||
|
|
||||
|
// Prevent the transition if the state is false
|
||||
|
transition('false => void', []), |
||||
|
|
||||
|
// Transition
|
||||
|
transition('* => void', animate('{{timings}}'), |
||||
|
{ |
||||
|
params: { |
||||
|
timings: `${FuseAnimationDurations.exiting} ${FuseAnimationCurves.acceleration}` |
||||
|
} |
||||
|
} |
||||
|
) |
||||
|
] |
||||
|
); |
||||
|
|
||||
|
export { fadeIn, fadeInTop, fadeInBottom, fadeInLeft, fadeInRight, fadeOut, fadeOutTop, fadeOutBottom, fadeOutLeft, fadeOutRight }; |
@ -0,0 +1 @@ |
|||||
|
export * from '@fuse/animations/public-api'; |
@ -0,0 +1,15 @@ |
|||||
|
import { expandCollapse } from '@fuse/animations/expand-collapse'; |
||||
|
import { fadeIn, fadeInBottom, fadeInLeft, fadeInRight, fadeInTop, fadeOut, fadeOutBottom, fadeOutLeft, fadeOutRight, fadeOutTop } from '@fuse/animations/fade'; |
||||
|
import { shake } from '@fuse/animations/shake'; |
||||
|
import { slideInBottom, slideInLeft, slideInRight, slideInTop, slideOutBottom, slideOutLeft, slideOutRight, slideOutTop } from '@fuse/animations/slide'; |
||||
|
import { zoomIn, zoomOut } from '@fuse/animations/zoom'; |
||||
|
|
||||
|
export const fuseAnimations = [ |
||||
|
expandCollapse, |
||||
|
fadeIn, fadeInTop, fadeInBottom, fadeInLeft, fadeInRight, |
||||
|
fadeOut, fadeOutTop, fadeOutBottom, fadeOutLeft, fadeOutRight, |
||||
|
shake, |
||||
|
slideInTop, slideInBottom, slideInLeft, slideInRight, |
||||
|
slideOutTop, slideOutBottom, slideOutLeft, slideOutRight, |
||||
|
zoomIn, zoomOut |
||||
|
]; |
@ -0,0 +1,73 @@ |
|||||
|
import { animate, keyframes, style, transition, trigger } from '@angular/animations'; |
||||
|
|
||||
|
// -----------------------------------------------------------------------------------------------------
|
||||
|
// @ Shake
|
||||
|
// -----------------------------------------------------------------------------------------------------
|
||||
|
const shake = trigger('shake', |
||||
|
[ |
||||
|
|
||||
|
// Prevent the transition if the state is false
|
||||
|
transition('void => false', []), |
||||
|
|
||||
|
// Transition
|
||||
|
transition('void => *, * => true', |
||||
|
[ |
||||
|
animate('{{timings}}', |
||||
|
keyframes([ |
||||
|
style({ |
||||
|
transform: 'translate3d(0, 0, 0)', |
||||
|
offset : 0 |
||||
|
}), |
||||
|
style({ |
||||
|
transform: 'translate3d(-10px, 0, 0)', |
||||
|
offset : 0.1 |
||||
|
}), |
||||
|
style({ |
||||
|
transform: 'translate3d(10px, 0, 0)', |
||||
|
offset : 0.2 |
||||
|
}), |
||||
|
style({ |
||||
|
transform: 'translate3d(-10px, 0, 0)', |
||||
|
offset : 0.3 |
||||
|
}), |
||||
|
style({ |
||||
|
transform: 'translate3d(10px, 0, 0)', |
||||
|
offset : 0.4 |
||||
|
}), |
||||
|
style({ |
||||
|
transform: 'translate3d(-10px, 0, 0)', |
||||
|
offset : 0.5 |
||||
|
}), |
||||
|
style({ |
||||
|
transform: 'translate3d(10px, 0, 0)', |
||||
|
offset : 0.6 |
||||
|
}), |
||||
|
style({ |
||||
|
transform: 'translate3d(-10px, 0, 0)', |
||||
|
offset : 0.7 |
||||
|
}), |
||||
|
style({ |
||||
|
transform: 'translate3d(10px, 0, 0)', |
||||
|
offset : 0.8 |
||||
|
}), |
||||
|
style({ |
||||
|
transform: 'translate3d(-10px, 0, 0)', |
||||
|
offset : 0.9 |
||||
|
}), |
||||
|
style({ |
||||
|
transform: 'translate3d(0, 0, 0)', |
||||
|
offset : 1 |
||||
|
}) |
||||
|
]) |
||||
|
) |
||||
|
], |
||||
|
{ |
||||
|
params: { |
||||
|
timings: '0.8s cubic-bezier(0.455, 0.03, 0.515, 0.955)' |
||||
|
} |
||||
|
} |
||||
|
) |
||||
|
] |
||||
|
); |
||||
|
|
||||
|
export { shake }; |
@ -0,0 +1,252 @@ |
|||||
|
import { animate, state, style, transition, trigger } from '@angular/animations'; |
||||
|
import { FuseAnimationCurves, FuseAnimationDurations } from '@fuse/animations/defaults'; |
||||
|
|
||||
|
// -----------------------------------------------------------------------------------------------------
|
||||
|
// @ Slide in top
|
||||
|
// -----------------------------------------------------------------------------------------------------
|
||||
|
const slideInTop = trigger('slideInTop', |
||||
|
[ |
||||
|
state('void', |
||||
|
style({ |
||||
|
transform: 'translate3d(0, -100%, 0)' |
||||
|
}) |
||||
|
), |
||||
|
|
||||
|
state('*', |
||||
|
style({ |
||||
|
transform: 'translate3d(0, 0, 0)' |
||||
|
}) |
||||
|
), |
||||
|
|
||||
|
// Prevent the transition if the state is false
|
||||
|
transition('void => false', []), |
||||
|
|
||||
|
// Transition
|
||||
|
transition('void => *', animate('{{timings}}'), |
||||
|
{ |
||||
|
params: { |
||||
|
timings: `${FuseAnimationDurations.entering} ${FuseAnimationCurves.deceleration}` |
||||
|
} |
||||
|
} |
||||
|
) |
||||
|
] |
||||
|
); |
||||
|
|
||||
|
// -----------------------------------------------------------------------------------------------------
|
||||
|
// @ Slide in bottom
|
||||
|
// -----------------------------------------------------------------------------------------------------
|
||||
|
const slideInBottom = trigger('slideInBottom', |
||||
|
[ |
||||
|
state('void', |
||||
|
style({ |
||||
|
transform: 'translate3d(0, 100%, 0)' |
||||
|
}) |
||||
|
), |
||||
|
|
||||
|
state('*', |
||||
|
style({ |
||||
|
transform: 'translate3d(0, 0, 0)' |
||||
|
}) |
||||
|
), |
||||
|
|
||||
|
// Prevent the transition if the state is false
|
||||
|
transition('void => false', []), |
||||
|
|
||||
|
// Transition
|
||||
|
transition('void => *', animate('{{timings}}'), |
||||
|
{ |
||||
|
params: { |
||||
|
timings: `${FuseAnimationDurations.entering} ${FuseAnimationCurves.deceleration}` |
||||
|
} |
||||
|
} |
||||
|
) |
||||
|
] |
||||
|
); |
||||
|
|
||||
|
// -----------------------------------------------------------------------------------------------------
|
||||
|
// @ Slide in left
|
||||
|
// -----------------------------------------------------------------------------------------------------
|
||||
|
const slideInLeft = trigger('slideInLeft', |
||||
|
[ |
||||
|
state('void', |
||||
|
style({ |
||||
|
transform: 'translate3d(-100%, 0, 0)' |
||||
|
}) |
||||
|
), |
||||
|
|
||||
|
state('*', |
||||
|
style({ |
||||
|
transform: 'translate3d(0, 0, 0)' |
||||
|
}) |
||||
|
), |
||||
|
|
||||
|
// Prevent the transition if the state is false
|
||||
|
transition('void => false', []), |
||||
|
|
||||
|
// Transition
|
||||
|
transition('void => *', animate('{{timings}}'), |
||||
|
{ |
||||
|
params: { |
||||
|
timings: `${FuseAnimationDurations.entering} ${FuseAnimationCurves.deceleration}` |
||||
|
} |
||||
|
} |
||||
|
) |
||||
|
] |
||||
|
); |
||||
|
|
||||
|
// -----------------------------------------------------------------------------------------------------
|
||||
|
// @ Slide in right
|
||||
|
// -----------------------------------------------------------------------------------------------------
|
||||
|
const slideInRight = trigger('slideInRight', |
||||
|
[ |
||||
|
state('void', |
||||
|
style({ |
||||
|
transform: 'translate3d(100%, 0, 0)' |
||||
|
}) |
||||
|
), |
||||
|
|
||||
|
state('*', |
||||
|
style({ |
||||
|
transform: 'translate3d(0, 0, 0)' |
||||
|
}) |
||||
|
), |
||||
|
|
||||
|
// Prevent the transition if the state is false
|
||||
|
transition('void => false', []), |
||||
|
|
||||
|
// Transition
|
||||
|
transition('void => *', animate('{{timings}}'), |
||||
|
{ |
||||
|
params: { |
||||
|
timings: `${FuseAnimationDurations.entering} ${FuseAnimationCurves.deceleration}` |
||||
|
} |
||||
|
} |
||||
|
) |
||||
|
] |
||||
|
); |
||||
|
|
||||
|
// -----------------------------------------------------------------------------------------------------
|
||||
|
// @ Slide out top
|
||||
|
// -----------------------------------------------------------------------------------------------------
|
||||
|
const slideOutTop = trigger('slideOutTop', |
||||
|
[ |
||||
|
state('*', |
||||
|
style({ |
||||
|
transform: 'translate3d(0, 0, 0)' |
||||
|
}) |
||||
|
), |
||||
|
|
||||
|
state('void', |
||||
|
style({ |
||||
|
transform: 'translate3d(0, -100%, 0)' |
||||
|
}) |
||||
|
), |
||||
|
|
||||
|
// Prevent the transition if the state is false
|
||||
|
transition('false => void', []), |
||||
|
|
||||
|
// Transition
|
||||
|
transition('* => void', animate('{{timings}}'), |
||||
|
{ |
||||
|
params: { |
||||
|
timings: `${FuseAnimationDurations.exiting} ${FuseAnimationCurves.acceleration}` |
||||
|
} |
||||
|
} |
||||
|
) |
||||
|
] |
||||
|
); |
||||
|
|
||||
|
// -----------------------------------------------------------------------------------------------------
|
||||
|
// @ Slide out bottom
|
||||
|
// -----------------------------------------------------------------------------------------------------
|
||||
|
const slideOutBottom = trigger('slideOutBottom', |
||||
|
[ |
||||
|
state('*', |
||||
|
style({ |
||||
|
transform: 'translate3d(0, 0, 0)' |
||||
|
}) |
||||
|
), |
||||
|
|
||||
|
state('void', |
||||
|
style({ |
||||
|
transform: 'translate3d(0, 100%, 0)' |
||||
|
}) |
||||
|
), |
||||
|
|
||||
|
// Prevent the transition if the state is false
|
||||
|
transition('false => void', []), |
||||
|
|
||||
|
// Transition
|
||||
|
transition('* => void', animate('{{timings}}'), |
||||
|
{ |
||||
|
params: { |
||||
|
timings: `${FuseAnimationDurations.exiting} ${FuseAnimationCurves.acceleration}` |
||||
|
} |
||||
|
} |
||||
|
) |
||||
|
] |
||||
|
); |
||||
|
|
||||
|
// -----------------------------------------------------------------------------------------------------
|
||||
|
// @ Slide out left
|
||||
|
// -----------------------------------------------------------------------------------------------------
|
||||
|
const slideOutLeft = trigger('slideOutLeft', |
||||
|
[ |
||||
|
state('*', |
||||
|
style({ |
||||
|
transform: 'translate3d(0, 0, 0)' |
||||
|
}) |
||||
|
), |
||||
|
|
||||
|
state('void', |
||||
|
style({ |
||||
|
transform: 'translate3d(-100%, 0, 0)' |
||||
|
}) |
||||
|
), |
||||
|
|
||||
|
// Prevent the transition if the state is false
|
||||
|
transition('false => void', []), |
||||
|
|
||||
|
// Transition
|
||||
|
transition('* => void', animate('{{timings}}'), |
||||
|
{ |
||||
|
params: { |
||||
|
timings: `${FuseAnimationDurations.exiting} ${FuseAnimationCurves.acceleration}` |
||||
|
} |
||||
|
} |
||||
|
) |
||||
|
] |
||||
|
); |
||||
|
|
||||
|
// -----------------------------------------------------------------------------------------------------
|
||||
|
// @ Slide out right
|
||||
|
// -----------------------------------------------------------------------------------------------------
|
||||
|
const slideOutRight = trigger('slideOutRight', |
||||
|
[ |
||||
|
state('*', |
||||
|
style({ |
||||
|
transform: 'translate3d(0, 0, 0)' |
||||
|
}) |
||||
|
), |
||||
|
|
||||
|
state('void', |
||||
|
style({ |
||||
|
transform: 'translate3d(100%, 0, 0)' |
||||
|
}) |
||||
|
), |
||||
|
|
||||
|
// Prevent the transition if the state is false
|
||||
|
transition('false => void', []), |
||||
|
|
||||
|
// Transition
|
||||
|
transition('* => void', animate('{{timings}}'), |
||||
|
{ |
||||
|
params: { |
||||
|
timings: `${FuseAnimationDurations.exiting} ${FuseAnimationCurves.acceleration}` |
||||
|
} |
||||
|
} |
||||
|
) |
||||
|
] |
||||
|
); |
||||
|
|
||||
|
export { slideInTop, slideInBottom, slideInLeft, slideInRight, slideOutTop, slideOutBottom, slideOutLeft, slideOutRight }; |
@ -0,0 +1,73 @@ |
|||||
|
import { animate, state, style, transition, trigger } from '@angular/animations'; |
||||
|
import { FuseAnimationCurves, FuseAnimationDurations } from '@fuse/animations/defaults'; |
||||
|
|
||||
|
// -----------------------------------------------------------------------------------------------------
|
||||
|
// @ Zoom in
|
||||
|
// -----------------------------------------------------------------------------------------------------
|
||||
|
const zoomIn = trigger('zoomIn', |
||||
|
[ |
||||
|
|
||||
|
state('void', |
||||
|
style({ |
||||
|
opacity : 0, |
||||
|
transform: 'scale(0.5)' |
||||
|
}) |
||||
|
), |
||||
|
|
||||
|
state('*', |
||||
|
style({ |
||||
|
opacity : 1, |
||||
|
transform: 'scale(1)' |
||||
|
}) |
||||
|
), |
||||
|
|
||||
|
// Prevent the transition if the state is false
|
||||
|
transition('void => false', []), |
||||
|
|
||||
|
// Transition
|
||||
|
transition('void => *', animate('{{timings}}'), |
||||
|
{ |
||||
|
params: { |
||||
|
timings: `${FuseAnimationDurations.entering} ${FuseAnimationCurves.deceleration}` |
||||
|
} |
||||
|
} |
||||
|
) |
||||
|
] |
||||
|
); |
||||
|
|
||||
|
// -----------------------------------------------------------------------------------------------------
|
||||
|
// @ Zoom out
|
||||
|
// -----------------------------------------------------------------------------------------------------
|
||||
|
const zoomOut = trigger('zoomOut', |
||||
|
[ |
||||
|
|
||||
|
state('*', |
||||
|
style({ |
||||
|
opacity : 1, |
||||
|
transform: 'scale(1)' |
||||
|
}) |
||||
|
), |
||||
|
|
||||
|
state('void', |
||||
|
style({ |
||||
|
opacity : 0, |
||||
|
transform: 'scale(0.5)' |
||||
|
}) |
||||
|
), |
||||
|
|
||||
|
// Prevent the transition if the state is false
|
||||
|
transition('false => void', []), |
||||
|
|
||||
|
// Transition
|
||||
|
transition('* => void', animate('{{timings}}'), |
||||
|
{ |
||||
|
params: { |
||||
|
timings: `${FuseAnimationDurations.exiting} ${FuseAnimationCurves.acceleration}` |
||||
|
} |
||||
|
} |
||||
|
) |
||||
|
] |
||||
|
); |
||||
|
|
||||
|
export { zoomIn, zoomOut }; |
||||
|
|
@ -0,0 +1,82 @@ |
|||||
|
<div |
||||
|
class="fuse-alert-container" |
||||
|
*ngIf="!dismissible || dismissible && !dismissed" |
||||
|
[@fadeIn]="!dismissed" |
||||
|
[@fadeOut]="!dismissed"> |
||||
|
|
||||
|
<!-- Border --> |
||||
|
<div |
||||
|
class="fuse-alert-border" |
||||
|
*ngIf="appearance === 'border'"></div> |
||||
|
|
||||
|
<!-- Icon --> |
||||
|
<div |
||||
|
class="fuse-alert-icon" |
||||
|
*ngIf="showIcon"> |
||||
|
|
||||
|
<!-- Custom icon --> |
||||
|
<div class="fuse-alert-custom-icon"> |
||||
|
<ng-content select="[fuseAlertIcon]"></ng-content> |
||||
|
</div> |
||||
|
|
||||
|
<!-- Default icons --> |
||||
|
<div class="fuse-alert-default-icon"> |
||||
|
|
||||
|
<mat-icon |
||||
|
*ngIf="type === 'primary'" |
||||
|
[svgIcon]="'heroicons_solid:check-circle'"></mat-icon> |
||||
|
|
||||
|
<mat-icon |
||||
|
*ngIf="type === 'accent'" |
||||
|
[svgIcon]="'heroicons_solid:check-circle'"></mat-icon> |
||||
|
|
||||
|
<mat-icon |
||||
|
*ngIf="type === 'warn'" |
||||
|
[svgIcon]="'heroicons_solid:x-circle'"></mat-icon> |
||||
|
|
||||
|
<mat-icon |
||||
|
*ngIf="type === 'basic'" |
||||
|
[svgIcon]="'heroicons_solid:check-circle'"></mat-icon> |
||||
|
|
||||
|
<mat-icon |
||||
|
*ngIf="type === 'info'" |
||||
|
[svgIcon]="'heroicons_solid:information-circle'"></mat-icon> |
||||
|
|
||||
|
<mat-icon |
||||
|
*ngIf="type === 'success'" |
||||
|
[svgIcon]="'heroicons_solid:check-circle'"></mat-icon> |
||||
|
|
||||
|
<mat-icon |
||||
|
*ngIf="type === 'warning'" |
||||
|
[svgIcon]="'heroicons_solid:exclamation'"></mat-icon> |
||||
|
|
||||
|
<mat-icon |
||||
|
*ngIf="type === 'error'" |
||||
|
[svgIcon]="'heroicons_solid:x-circle'"></mat-icon> |
||||
|
|
||||
|
</div> |
||||
|
|
||||
|
</div> |
||||
|
|
||||
|
<!-- Content --> |
||||
|
<div class="fuse-alert-content"> |
||||
|
|
||||
|
<div class="fuse-alert-title"> |
||||
|
<ng-content select="[fuseAlertTitle]"></ng-content> |
||||
|
</div> |
||||
|
|
||||
|
<div class="fuse-alert-message"> |
||||
|
<ng-content></ng-content> |
||||
|
</div> |
||||
|
|
||||
|
</div> |
||||
|
|
||||
|
<!-- Dismiss button --> |
||||
|
<button |
||||
|
class="fuse-alert-dismiss-button" |
||||
|
mat-icon-button |
||||
|
(click)="dismiss()"> |
||||
|
<mat-icon [svgIcon]="'heroicons_solid:x'"></mat-icon> |
||||
|
</button> |
||||
|
|
||||
|
</div> |
File diff suppressed because it is too large
@ -0,0 +1,213 @@ |
|||||
|
import { ChangeDetectionStrategy, ChangeDetectorRef, Component, EventEmitter, HostBinding, Input, OnChanges, OnDestroy, OnInit, Output, SimpleChanges, ViewEncapsulation } from '@angular/core'; |
||||
|
import { Subject } from 'rxjs'; |
||||
|
import { filter, takeUntil } from 'rxjs/operators'; |
||||
|
import { BooleanInput, coerceBooleanProperty } from '@angular/cdk/coercion'; |
||||
|
import { fuseAnimations } from '@fuse/animations'; |
||||
|
import { FuseAlertAppearance, FuseAlertType } from '@fuse/components/alert/alert.types'; |
||||
|
import { FuseAlertService } from '@fuse/components/alert/alert.service'; |
||||
|
import { FuseUtilsService } from '@fuse/services/utils/utils.service'; |
||||
|
|
||||
|
@Component({ |
||||
|
selector : 'fuse-alert', |
||||
|
templateUrl : './alert.component.html', |
||||
|
styleUrls : ['./alert.component.scss'], |
||||
|
encapsulation : ViewEncapsulation.None, |
||||
|
changeDetection: ChangeDetectionStrategy.OnPush, |
||||
|
animations : fuseAnimations, |
||||
|
exportAs : 'fuseAlert' |
||||
|
}) |
||||
|
export class FuseAlertComponent implements OnChanges, OnInit, OnDestroy |
||||
|
{ |
||||
|
/* eslint-disable @typescript-eslint/naming-convention */ |
||||
|
static ngAcceptInputType_dismissible: BooleanInput; |
||||
|
static ngAcceptInputType_dismissed: BooleanInput; |
||||
|
static ngAcceptInputType_showIcon: BooleanInput; |
||||
|
/* eslint-enable @typescript-eslint/naming-convention */ |
||||
|
|
||||
|
@Input() appearance: FuseAlertAppearance = 'soft'; |
||||
|
@Input() dismissed: boolean = false; |
||||
|
@Input() dismissible: boolean = false; |
||||
|
@Input() name: string = this._fuseUtilsService.randomId(); |
||||
|
@Input() showIcon: boolean = true; |
||||
|
@Input() type: FuseAlertType = 'primary'; |
||||
|
@Output() readonly dismissedChanged: EventEmitter<boolean> = new EventEmitter<boolean>(); |
||||
|
|
||||
|
private _unsubscribeAll: Subject<any> = new Subject<any>(); |
||||
|
|
||||
|
/** |
||||
|
* Constructor |
||||
|
*/ |
||||
|
constructor( |
||||
|
private _changeDetectorRef: ChangeDetectorRef, |
||||
|
private _fuseAlertService: FuseAlertService, |
||||
|
private _fuseUtilsService: FuseUtilsService |
||||
|
) |
||||
|
{ |
||||
|
} |
||||
|
|
||||
|
// -----------------------------------------------------------------------------------------------------
|
||||
|
// @ Accessors
|
||||
|
// -----------------------------------------------------------------------------------------------------
|
||||
|
|
||||
|
/** |
||||
|
* Host binding for component classes |
||||
|
*/ |
||||
|
@HostBinding('class') get classList(): any |
||||
|
{ |
||||
|
return { |
||||
|
'fuse-alert-appearance-border' : this.appearance === 'border', |
||||
|
'fuse-alert-appearance-fill' : this.appearance === 'fill', |
||||
|
'fuse-alert-appearance-outline': this.appearance === 'outline', |
||||
|
'fuse-alert-appearance-soft' : this.appearance === 'soft', |
||||
|
'fuse-alert-dismissed' : this.dismissed, |
||||
|
'fuse-alert-dismissible' : this.dismissible, |
||||
|
'fuse-alert-show-icon' : this.showIcon, |
||||
|
'fuse-alert-type-primary' : this.type === 'primary', |
||||
|
'fuse-alert-type-accent' : this.type === 'accent', |
||||
|
'fuse-alert-type-warn' : this.type === 'warn', |
||||
|
'fuse-alert-type-basic' : this.type === 'basic', |
||||
|
'fuse-alert-type-info' : this.type === 'info', |
||||
|
'fuse-alert-type-success' : this.type === 'success', |
||||
|
'fuse-alert-type-warning' : this.type === 'warning', |
||||
|
'fuse-alert-type-error' : this.type === 'error' |
||||
|
}; |
||||
|
} |
||||
|
|
||||
|
// -----------------------------------------------------------------------------------------------------
|
||||
|
// @ Lifecycle hooks
|
||||
|
// -----------------------------------------------------------------------------------------------------
|
||||
|
|
||||
|
/** |
||||
|
* On changes |
||||
|
* |
||||
|
* @param changes |
||||
|
*/ |
||||
|
ngOnChanges(changes: SimpleChanges): void |
||||
|
{ |
||||
|
// Dismissed
|
||||
|
if ( 'dismissed' in changes ) |
||||
|
{ |
||||
|
// Coerce the value to a boolean
|
||||
|
this.dismissed = coerceBooleanProperty(changes.dismissed.currentValue); |
||||
|
|
||||
|
// Dismiss/show the alert
|
||||
|
this._toggleDismiss(this.dismissed); |
||||
|
} |
||||
|
|
||||
|
// Dismissible
|
||||
|
if ( 'dismissible' in changes ) |
||||
|
{ |
||||
|
// Coerce the value to a boolean
|
||||
|
this.dismissible = coerceBooleanProperty(changes.dismissible.currentValue); |
||||
|
} |
||||
|
|
||||
|
// Show icon
|
||||
|
if ( 'showIcon' in changes ) |
||||
|
{ |
||||
|
// Coerce the value to a boolean
|
||||
|
this.showIcon = coerceBooleanProperty(changes.showIcon.currentValue); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* On init |
||||
|
*/ |
||||
|
ngOnInit(): void |
||||
|
{ |
||||
|
// Subscribe to the dismiss calls
|
||||
|
this._fuseAlertService.onDismiss |
||||
|
.pipe( |
||||
|
filter(name => this.name === name), |
||||
|
takeUntil(this._unsubscribeAll) |
||||
|
) |
||||
|
.subscribe(() => { |
||||
|
|
||||
|
// Dismiss the alert
|
||||
|
this.dismiss(); |
||||
|
}); |
||||
|
|
||||
|
// Subscribe to the show calls
|
||||
|
this._fuseAlertService.onShow |
||||
|
.pipe( |
||||
|
filter(name => this.name === name), |
||||
|
takeUntil(this._unsubscribeAll) |
||||
|
) |
||||
|
.subscribe(() => { |
||||
|
|
||||
|
// Show the alert
|
||||
|
this.show(); |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* On destroy |
||||
|
*/ |
||||
|
ngOnDestroy(): void |
||||
|
{ |
||||
|
// Unsubscribe from all subscriptions
|
||||
|
this._unsubscribeAll.next(); |
||||
|
this._unsubscribeAll.complete(); |
||||
|
} |
||||
|
|
||||
|
// -----------------------------------------------------------------------------------------------------
|
||||
|
// @ Public methods
|
||||
|
// -----------------------------------------------------------------------------------------------------
|
||||
|
|
||||
|
/** |
||||
|
* Dismiss the alert |
||||
|
*/ |
||||
|
dismiss(): void |
||||
|
{ |
||||
|
// Return if the alert is already dismissed
|
||||
|
if ( this.dismissed ) |
||||
|
{ |
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
// Dismiss the alert
|
||||
|
this._toggleDismiss(true); |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* Show the dismissed alert |
||||
|
*/ |
||||
|
show(): void |
||||
|
{ |
||||
|
// Return if the alert is already showing
|
||||
|
if ( !this.dismissed ) |
||||
|
{ |
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
// Show the alert
|
||||
|
this._toggleDismiss(false); |
||||
|
} |
||||
|
|
||||
|
// -----------------------------------------------------------------------------------------------------
|
||||
|
// @ Private methods
|
||||
|
// -----------------------------------------------------------------------------------------------------
|
||||
|
|
||||
|
/** |
||||
|
* Dismiss/show the alert |
||||
|
* |
||||
|
* @param dismissed |
||||
|
* @private |
||||
|
*/ |
||||
|
private _toggleDismiss(dismissed: boolean): void |
||||
|
{ |
||||
|
// Return if the alert is not dismissible
|
||||
|
if ( !this.dismissible ) |
||||
|
{ |
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
// Set the dismissed
|
||||
|
this.dismissed = dismissed; |
||||
|
|
||||
|
// Execute the observable
|
||||
|
this.dismissedChanged.next(this.dismissed); |
||||
|
|
||||
|
// Notify the change detector
|
||||
|
this._changeDetectorRef.markForCheck(); |
||||
|
} |
||||
|
} |
@ -0,0 +1,22 @@ |
|||||
|
import { NgModule } from '@angular/core'; |
||||
|
import { CommonModule } from '@angular/common'; |
||||
|
import { MatButtonModule } from '@angular/material/button'; |
||||
|
import { MatIconModule } from '@angular/material/icon'; |
||||
|
import { FuseAlertComponent } from '@fuse/components/alert/alert.component'; |
||||
|
|
||||
|
@NgModule({ |
||||
|
declarations: [ |
||||
|
FuseAlertComponent |
||||
|
], |
||||
|
imports : [ |
||||
|
CommonModule, |
||||
|
MatButtonModule, |
||||
|
MatIconModule |
||||
|
], |
||||
|
exports : [ |
||||
|
FuseAlertComponent |
||||
|
] |
||||
|
}) |
||||
|
export class FuseAlertModule |
||||
|
{ |
||||
|
} |
@ -0,0 +1,77 @@ |
|||||
|
import { Injectable } from '@angular/core'; |
||||
|
import { Observable, ReplaySubject } from 'rxjs'; |
||||
|
|
||||
|
@Injectable({ |
||||
|
providedIn: 'root' |
||||
|
}) |
||||
|
export class FuseAlertService |
||||
|
{ |
||||
|
private readonly _onDismiss: ReplaySubject<string> = new ReplaySubject<string>(1); |
||||
|
private readonly _onShow: ReplaySubject<string> = new ReplaySubject<string>(1); |
||||
|
|
||||
|
/** |
||||
|
* Constructor |
||||
|
*/ |
||||
|
constructor() |
||||
|
{ |
||||
|
} |
||||
|
|
||||
|
// -----------------------------------------------------------------------------------------------------
|
||||
|
// @ Accessors
|
||||
|
// -----------------------------------------------------------------------------------------------------
|
||||
|
|
||||
|
/** |
||||
|
* Getter for onDismiss |
||||
|
*/ |
||||
|
get onDismiss(): Observable<any> |
||||
|
{ |
||||
|
return this._onDismiss.asObservable(); |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* Getter for onShow |
||||
|
*/ |
||||
|
get onShow(): Observable<any> |
||||
|
{ |
||||
|
return this._onShow.asObservable(); |
||||
|
} |
||||
|
|
||||
|
// -----------------------------------------------------------------------------------------------------
|
||||
|
// @ Public methods
|
||||
|
// -----------------------------------------------------------------------------------------------------
|
||||
|
|
||||
|
/** |
||||
|
* Dismiss the alert |
||||
|
* |
||||
|
* @param name |
||||
|
*/ |
||||
|
dismiss(name: string): void |
||||
|
{ |
||||
|
// Return if the name is not provided
|
||||
|
if ( !name ) |
||||
|
{ |
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
// Execute the observable
|
||||
|
this._onDismiss.next(name); |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* Show the dismissed alert |
||||
|
* |
||||
|
* @param name |
||||
|
*/ |
||||
|
show(name: string): void |
||||
|
{ |
||||
|
// Return if the name is not provided
|
||||
|
if ( !name ) |
||||
|
{ |
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
// Execute the observable
|
||||
|
this._onShow.next(name); |
||||
|
} |
||||
|
|
||||
|
} |
@ -0,0 +1,15 @@ |
|||||
|
export type FuseAlertAppearance = |
||||
|
| 'border' |
||||
|
| 'fill' |
||||
|
| 'outline' |
||||
|
| 'soft'; |
||||
|
|
||||
|
export type FuseAlertType = |
||||
|
| 'primary' |
||||
|
| 'accent' |
||||
|
| 'warn' |
||||
|
| 'basic' |
||||
|
| 'info' |
||||
|
| 'success' |
||||
|
| 'warning' |
||||
|
| 'error'; |
@ -0,0 +1 @@ |
|||||
|
export * from '@fuse/components/alert/public-api'; |
@ -0,0 +1,4 @@ |
|||||
|
export * from '@fuse/components/alert/alert.component'; |
||||
|
export * from '@fuse/components/alert/alert.module'; |
||||
|
export * from '@fuse/components/alert/alert.service'; |
||||
|
export * from '@fuse/components/alert/alert.types'; |
@ -0,0 +1,30 @@ |
|||||
|
<!-- Flippable card --> |
||||
|
<ng-container *ngIf="flippable"> |
||||
|
|
||||
|
<!-- Front --> |
||||
|
<div class="fuse-card-front"> |
||||
|
<ng-content select="[fuseCardFront]"></ng-content> |
||||
|
</div> |
||||
|
|
||||
|
<!-- Back --> |
||||
|
<div class="fuse-card-back"> |
||||
|
<ng-content select="[fuseCardBack]"></ng-content> |
||||
|
</div> |
||||
|
|
||||
|
</ng-container> |
||||
|
|
||||
|
<!-- Normal card --> |
||||
|
<ng-container *ngIf="!flippable"> |
||||
|
|
||||
|
<!-- Content --> |
||||
|
<ng-content></ng-content> |
||||
|
|
||||
|
<!-- Expansion --> |
||||
|
<div |
||||
|
class="fuse-card-expansion" |
||||
|
*ngIf="expanded" |
||||
|
[@expandCollapse]> |
||||
|
<ng-content select="[fuseCardExpansion]"></ng-content> |
||||
|
</div> |
||||
|
|
||||
|
</ng-container> |
@ -0,0 +1,63 @@ |
|||||
|
fuse-card { |
||||
|
position: relative; |
||||
|
display: flex; |
||||
|
overflow: hidden; |
||||
|
@apply rounded-2xl shadow bg-card; |
||||
|
|
||||
|
/* Flippable */ |
||||
|
&.fuse-card-flippable { |
||||
|
border-radius: 0; |
||||
|
overflow: visible; |
||||
|
transform-style: preserve-3d; |
||||
|
transition: transform 1s; |
||||
|
perspective: 600px; |
||||
|
background: transparent; |
||||
|
@apply shadow-none; |
||||
|
|
||||
|
&.fuse-card-face-back { |
||||
|
|
||||
|
.fuse-card-front { |
||||
|
visibility: hidden; |
||||
|
opacity: 0; |
||||
|
transform: rotateY(180deg); |
||||
|
} |
||||
|
|
||||
|
.fuse-card-back { |
||||
|
visibility: visible; |
||||
|
opacity: 1; |
||||
|
transform: rotateY(360deg); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
.fuse-card-front, |
||||
|
.fuse-card-back { |
||||
|
display: flex; |
||||
|
flex-direction: column; |
||||
|
flex: 1 1 auto; |
||||
|
z-index: 10; |
||||
|
transition: transform 0.5s ease-out 0s, visibility 0s ease-in 0.2s, opacity 0s ease-in 0.2s; |
||||
|
backface-visibility: hidden; |
||||
|
@apply rounded-2xl shadow bg-card; |
||||
|
} |
||||
|
|
||||
|
.fuse-card-front { |
||||
|
position: relative; |
||||
|
opacity: 1; |
||||
|
visibility: visible; |
||||
|
transform: rotateY(0deg); |
||||
|
overflow: hidden; |
||||
|
} |
||||
|
|
||||
|
.fuse-card-back { |
||||
|
position: absolute; |
||||
|
top: 0; |
||||
|
right: 0; |
||||
|
bottom: 0; |
||||
|
left: 0; |
||||
|
opacity: 0; |
||||
|
visibility: hidden; |
||||
|
transform: rotateY(180deg); |
||||
|
overflow: hidden auto; |
||||
|
} |
||||
|
} |
||||
|
} |
@ -0,0 +1,74 @@ |
|||||
|
import { Component, HostBinding, Input, OnChanges, SimpleChanges, ViewEncapsulation } from '@angular/core'; |
||||
|
import { BooleanInput, coerceBooleanProperty } from '@angular/cdk/coercion'; |
||||
|
import { fuseAnimations } from '@fuse/animations'; |
||||
|
import { FuseCardFace } from '@fuse/components/card/card.types'; |
||||
|
|
||||
|
@Component({ |
||||
|
selector : 'fuse-card', |
||||
|
templateUrl : './card.component.html', |
||||
|
styleUrls : ['./card.component.scss'], |
||||
|
encapsulation: ViewEncapsulation.None, |
||||
|
animations : fuseAnimations, |
||||
|
exportAs : 'fuseCard' |
||||
|
}) |
||||
|
export class FuseCardComponent implements OnChanges |
||||
|
{ |
||||
|
/* eslint-disable @typescript-eslint/naming-convention */ |
||||
|
static ngAcceptInputType_expanded: BooleanInput; |
||||
|
static ngAcceptInputType_flippable: BooleanInput; |
||||
|
/* eslint-enable @typescript-eslint/naming-convention */ |
||||
|
|
||||
|
@Input() expanded: boolean = false; |
||||
|
@Input() face: FuseCardFace = 'front'; |
||||
|
@Input() flippable: boolean = false; |
||||
|
|
||||
|
/** |
||||
|
* Constructor |
||||
|
*/ |
||||
|
constructor() |
||||
|
{ |
||||
|
} |
||||
|
|
||||
|
// -----------------------------------------------------------------------------------------------------
|
||||
|
// @ Accessors
|
||||
|
// -----------------------------------------------------------------------------------------------------
|
||||
|
|
||||
|
/** |
||||
|
* Host binding for component classes |
||||
|
*/ |
||||
|
@HostBinding('class') get classList(): any |
||||
|
{ |
||||
|
return { |
||||
|
'fuse-card-expanded' : this.expanded, |
||||
|
'fuse-card-face-back' : this.flippable && this.face === 'back', |
||||
|
'fuse-card-face-front': this.flippable && this.face === 'front', |
||||
|
'fuse-card-flippable' : this.flippable |
||||
|
}; |
||||
|
} |
||||
|
|
||||
|
// -----------------------------------------------------------------------------------------------------
|
||||
|
// @ Lifecycle hooks
|
||||
|
// -----------------------------------------------------------------------------------------------------
|
||||
|
|
||||
|
/** |
||||
|
* On changes |
||||
|
* |
||||
|
* @param changes |
||||
|
*/ |
||||
|
ngOnChanges(changes: SimpleChanges): void |
||||
|
{ |
||||
|
// Expanded
|
||||
|
if ( 'expanded' in changes ) |
||||
|
{ |
||||
|
// Coerce the value to a boolean
|
||||
|
this.expanded = coerceBooleanProperty(changes.expanded.currentValue); |
||||
|
} |
||||
|
|
||||
|
// Flippable
|
||||
|
if ( 'flippable' in changes ) |
||||
|
{ |
||||
|
// Coerce the value to a boolean
|
||||
|
this.flippable = coerceBooleanProperty(changes.flippable.currentValue); |
||||
|
} |
||||
|
} |
||||
|
} |
@ -0,0 +1,18 @@ |
|||||
|
import { NgModule } from '@angular/core'; |
||||
|
import { CommonModule } from '@angular/common'; |
||||
|
import { FuseCardComponent } from '@fuse/components/card/card.component'; |
||||
|
|
||||
|
@NgModule({ |
||||
|
declarations: [ |
||||
|
FuseCardComponent |
||||
|
], |
||||
|
imports : [ |
||||
|
CommonModule |
||||
|
], |
||||
|
exports : [ |
||||
|
FuseCardComponent |
||||
|
] |
||||
|
}) |
||||
|
export class FuseCardModule |
||||
|
{ |
||||
|
} |
@ -0,0 +1,3 @@ |
|||||
|
export type FuseCardFace = |
||||
|
| 'front' |
||||
|
| 'back'; |
@ -0,0 +1 @@ |
|||||
|
export * from '@fuse/components/card/public-api'; |
@ -0,0 +1,2 @@ |
|||||
|
export * from '@fuse/components/card/card.component'; |
||||
|
export * from '@fuse/components/card/card.module'; |
@ -0,0 +1,101 @@ |
|||||
|
<div |
||||
|
class="range" |
||||
|
(click)="openPickerPanel()" |
||||
|
#pickerPanelOrigin> |
||||
|
|
||||
|
<div class="start"> |
||||
|
<div class="date">{{range.startDate}}</div> |
||||
|
<div |
||||
|
class="time" |
||||
|
*ngIf="range.startTime">{{range.startTime}}</div> |
||||
|
</div> |
||||
|
|
||||
|
<div class="separator">-</div> |
||||
|
|
||||
|
<div class="end"> |
||||
|
<div class="date">{{range.endDate}}</div> |
||||
|
<div |
||||
|
class="time" |
||||
|
*ngIf="range.endTime">{{range.endTime}}</div> |
||||
|
</div> |
||||
|
|
||||
|
</div> |
||||
|
|
||||
|
<ng-template #pickerPanel> |
||||
|
|
||||
|
<!-- Start --> |
||||
|
<div class="start"> |
||||
|
|
||||
|
<div class="month"> |
||||
|
<div class="month-header"> |
||||
|
<button |
||||
|
class="previous-button" |
||||
|
mat-icon-button |
||||
|
(click)="prev()" |
||||
|
tabindex="1"> |
||||
|
<mat-icon [svgIcon]="'heroicons_outline:chevron-left'"></mat-icon> |
||||
|
</button> |
||||
|
<div class="month-label">{{getMonthLabel(1)}}</div> |
||||
|
</div> |
||||
|
<mat-month-view |
||||
|
[(activeDate)]="activeDates.month1" |
||||
|
[dateFilter]="dateFilter()" |
||||
|
[dateClass]="dateClass()" |
||||
|
(click)="$event.stopImmediatePropagation()" |
||||
|
(selectedChange)="onSelectedDateChange($event)" |
||||
|
#matMonthView1> |
||||
|
</mat-month-view> |
||||
|
</div> |
||||
|
|
||||
|
<mat-form-field |
||||
|
class="fuse-mat-no-subscript time start-time" |
||||
|
*ngIf="timeRange"> |
||||
|
<input |
||||
|
matInput |
||||
|
[autocomplete]="'off'" |
||||
|
[formControl]="startTimeFormControl" |
||||
|
(blur)="updateStartTime($event)" |
||||
|
tabindex="3"> |
||||
|
<mat-label>Start time</mat-label> |
||||
|
</mat-form-field> |
||||
|
|
||||
|
</div> |
||||
|
|
||||
|
<!-- End --> |
||||
|
<div class="end"> |
||||
|
|
||||
|
<div class="month"> |
||||
|
<div class="month-header"> |
||||
|
<div class="month-label">{{getMonthLabel(2)}}</div> |
||||
|
<button |
||||
|
class="next-button" |
||||
|
mat-icon-button |
||||
|
(click)="next()" |
||||
|
tabindex="2"> |
||||
|
<mat-icon [svgIcon]="'heroicons_outline:chevron-right'"></mat-icon> |
||||
|
</button> |
||||
|
</div> |
||||
|
<mat-month-view |
||||
|
[(activeDate)]="activeDates.month2" |
||||
|
[dateFilter]="dateFilter()" |
||||
|
[dateClass]="dateClass()" |
||||
|
(click)="$event.stopImmediatePropagation()" |
||||
|
(selectedChange)="onSelectedDateChange($event)" |
||||
|
#matMonthView2> |
||||
|
</mat-month-view> |
||||
|
</div> |
||||
|
|
||||
|
<mat-form-field |
||||
|
class="fuse-mat-no-subscript time end-time" |
||||
|
*ngIf="timeRange"> |
||||
|
<input |
||||
|
matInput |
||||
|
[formControl]="endTimeFormControl" |
||||
|
(blur)="updateEndTime($event)" |
||||
|
tabindex="4"> |
||||
|
<mat-label>End time</mat-label> |
||||
|
</mat-form-field> |
||||
|
|
||||
|
</div> |
||||
|
|
||||
|
</ng-template> |
@ -0,0 +1,292 @@ |
|||||
|
/* Variables */ |
||||
|
$body-cell-padding: 2px; |
||||
|
|
||||
|
fuse-date-range { |
||||
|
display: flex; |
||||
|
|
||||
|
.range { |
||||
|
display: flex; |
||||
|
align-items: center; |
||||
|
height: 48px; |
||||
|
min-height: 48px; |
||||
|
max-height: 48px; |
||||
|
cursor: pointer; |
||||
|
|
||||
|
.start, |
||||
|
.end { |
||||
|
display: flex; |
||||
|
align-items: center; |
||||
|
height: 100%; |
||||
|
padding: 0 16px; |
||||
|
border-radius: 6px; |
||||
|
border-width: 1px; |
||||
|
line-height: 1; |
||||
|
@apply shadow-sm border-gray-300 dark:bg-black dark:bg-opacity-5 dark:border-gray-500; |
||||
|
|
||||
|
.date { |
||||
|
white-space: nowrap; |
||||
|
|
||||
|
+ .time { |
||||
|
margin-left: 8px; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
.time { |
||||
|
white-space: nowrap; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
.separator { |
||||
|
margin: 0 2px; |
||||
|
|
||||
|
@screen sm { |
||||
|
margin: 0 12px; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
.fuse-date-range-panel { |
||||
|
border-radius: 4px; |
||||
|
padding: 24px; |
||||
|
@apply shadow-2xl bg-card; |
||||
|
|
||||
|
.start, |
||||
|
.end { |
||||
|
display: flex; |
||||
|
flex-direction: column; |
||||
|
|
||||
|
.month { |
||||
|
max-width: 196px; |
||||
|
min-width: 196px; |
||||
|
width: 196px; |
||||
|
|
||||
|
.month-header { |
||||
|
position: relative; |
||||
|
display: flex; |
||||
|
align-items: center; |
||||
|
justify-content: center; |
||||
|
height: 32px; |
||||
|
margin-bottom: 16px; |
||||
|
|
||||
|
.previous-button, |
||||
|
.next-button { |
||||
|
position: absolute; |
||||
|
width: 24px !important; |
||||
|
height: 24px !important; |
||||
|
min-height: 24px !important; |
||||
|
max-height: 24px !important; |
||||
|
line-height: 24px !important; |
||||
|
|
||||
|
.mat-icon { |
||||
|
@apply icon-size-5; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
.previous-button { |
||||
|
left: 0; |
||||
|
} |
||||
|
|
||||
|
.next-button { |
||||
|
right: 0; |
||||
|
} |
||||
|
|
||||
|
.month-label { |
||||
|
font-weight: 500; |
||||
|
@apply text-secondary; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
mat-month-view { |
||||
|
display: flex; |
||||
|
min-height: 188px; |
||||
|
|
||||
|
.mat-calendar-table { |
||||
|
width: 100%; |
||||
|
border-collapse: collapse; |
||||
|
|
||||
|
tbody { |
||||
|
|
||||
|
tr { |
||||
|
|
||||
|
&[aria-hidden=true] { |
||||
|
display: none !important; |
||||
|
} |
||||
|
|
||||
|
&:first-child { |
||||
|
|
||||
|
td:first-child { |
||||
|
|
||||
|
&[aria-hidden=true] { |
||||
|
visibility: hidden; |
||||
|
pointer-events: none; |
||||
|
opacity: 0; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
td, |
||||
|
td:hover { |
||||
|
|
||||
|
&.fuse-date-range { |
||||
|
|
||||
|
&:before { |
||||
|
@apply bg-primary-200; |
||||
|
} |
||||
|
|
||||
|
.mat-calendar-body-cell-content { |
||||
|
background-color: transparent; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
&.fuse-date-range-start, |
||||
|
&.fuse-date-range-end { |
||||
|
|
||||
|
.mat-calendar-body-cell-content { |
||||
|
@apply bg-primary text-on-primary; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
.mat-calendar-body-today { |
||||
|
border: none; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
td.mat-calendar-body-cell { |
||||
|
width: 28px !important; |
||||
|
height: 28px !important; |
||||
|
padding: $body-cell-padding !important; |
||||
|
|
||||
|
&.fuse-date-range { |
||||
|
position: relative; |
||||
|
|
||||
|
&:before { |
||||
|
content: ''; |
||||
|
position: absolute; |
||||
|
top: $body-cell-padding; |
||||
|
right: 0; |
||||
|
bottom: $body-cell-padding; |
||||
|
left: 0; |
||||
|
} |
||||
|
|
||||
|
&.fuse-date-range-start { |
||||
|
|
||||
|
&:before { |
||||
|
left: $body-cell-padding; |
||||
|
border-radius: 999px 0 0 999px; |
||||
|
} |
||||
|
|
||||
|
&.fuse-date-range-end, |
||||
|
&:last-child { |
||||
|
|
||||
|
&:before { |
||||
|
right: $body-cell-padding; |
||||
|
border-radius: 999px; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
&.fuse-date-range-end { |
||||
|
|
||||
|
&:before { |
||||
|
right: $body-cell-padding; |
||||
|
border-radius: 0 999px 999px 0; |
||||
|
} |
||||
|
|
||||
|
&:first-child { |
||||
|
|
||||
|
&:before { |
||||
|
left: $body-cell-padding; |
||||
|
border-radius: 999px; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
&:first-child { |
||||
|
|
||||
|
&:before { |
||||
|
border-radius: 999px 0 0 999px; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
&:last-child { |
||||
|
|
||||
|
&:before { |
||||
|
border-radius: 0 999px 999px 0; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
.mat-calendar-body-cell-content { |
||||
|
position: relative; |
||||
|
top: 0; |
||||
|
left: 0; |
||||
|
width: 24px; |
||||
|
height: 24px; |
||||
|
font-size: 12px; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
td.mat-calendar-body-label { |
||||
|
|
||||
|
+ td.mat-calendar-body-cell { |
||||
|
|
||||
|
&.fuse-date-range { |
||||
|
|
||||
|
&:before { |
||||
|
border-radius: 999px 0 0 999px; |
||||
|
} |
||||
|
|
||||
|
&.fuse-date-range-start { |
||||
|
|
||||
|
&.fuse-date-range-end { |
||||
|
border-radius: 999px; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
&.fuse-date-range-end { |
||||
|
|
||||
|
&:before { |
||||
|
left: $body-cell-padding; |
||||
|
border-radius: 999px; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
.time { |
||||
|
width: 100%; |
||||
|
max-width: 196px; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
.start { |
||||
|
align-items: flex-start; |
||||
|
margin-right: 20px; |
||||
|
|
||||
|
.month { |
||||
|
|
||||
|
.month-label { |
||||
|
margin-left: 8px; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
.end { |
||||
|
align-items: flex-end; |
||||
|
margin-left: 20px; |
||||
|
|
||||
|
.month { |
||||
|
|
||||
|
.month-label { |
||||
|
margin-right: 8px; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
@ -0,0 +1,682 @@ |
|||||
|
import { ChangeDetectorRef, Component, ElementRef, EventEmitter, forwardRef, HostBinding, Input, OnDestroy, OnInit, Output, Renderer2, TemplateRef, ViewChild, ViewContainerRef, ViewEncapsulation } from '@angular/core'; |
||||
|
import { ControlValueAccessor, FormControl, NG_VALUE_ACCESSOR, Validators } from '@angular/forms'; |
||||
|
import { Overlay } from '@angular/cdk/overlay'; |
||||
|
import { TemplatePortal } from '@angular/cdk/portal'; |
||||
|
import { MatCalendarCellCssClasses, MatMonthView } from '@angular/material/datepicker'; |
||||
|
import { Subject } from 'rxjs'; |
||||
|
import * as moment from 'moment'; |
||||
|
import { Moment } from 'moment'; |
||||
|
|
||||
|
@Component({ |
||||
|
selector : 'fuse-date-range', |
||||
|
templateUrl : './date-range.component.html', |
||||
|
styleUrls : ['./date-range.component.scss'], |
||||
|
encapsulation: ViewEncapsulation.None, |
||||
|
exportAs : 'fuseDateRange', |
||||
|
providers : [ |
||||
|
{ |
||||
|
provide : NG_VALUE_ACCESSOR, |
||||
|
useExisting: forwardRef(() => FuseDateRangeComponent), |
||||
|
multi : true |
||||
|
} |
||||
|
] |
||||
|
}) |
||||
|
export class FuseDateRangeComponent implements ControlValueAccessor, OnInit, OnDestroy |
||||
|
{ |
||||
|
@Output() readonly rangeChanged: EventEmitter<{ start: string; end: string }> = new EventEmitter<{ start: string; end: string }>(); |
||||
|
@ViewChild('matMonthView1') private _matMonthView1: MatMonthView<any>; |
||||
|
@ViewChild('matMonthView2') private _matMonthView2: MatMonthView<any>; |
||||
|
@ViewChild('pickerPanelOrigin', {read: ElementRef}) private _pickerPanelOrigin: ElementRef; |
||||
|
@ViewChild('pickerPanel') private _pickerPanel: TemplateRef<any>; |
||||
|
@HostBinding('class.fuse-date-range') private _defaultClassNames = true; |
||||
|
|
||||
|
activeDates: { month1: Moment | null; month2: Moment | null } = { |
||||
|
month1: null, |
||||
|
month2: null |
||||
|
}; |
||||
|
setWhichDate: 'start' | 'end' = 'start'; |
||||
|
startTimeFormControl: FormControl; |
||||
|
endTimeFormControl: FormControl; |
||||
|
private _dateFormat: string; |
||||
|
private _onChange: (value: any) => void; |
||||
|
private _onTouched: (value: any) => void; |
||||
|
private _programmaticChange!: boolean; |
||||
|
private _range: { start: Moment | null; end: Moment | null } = { |
||||
|
start: null, |
||||
|
end : null |
||||
|
}; |
||||
|
private _timeFormat: string; |
||||
|
private _timeRange: boolean; |
||||
|
private readonly _timeRegExp: RegExp = new RegExp('^(0[0-9]|1[0-9]|2[0-4]|[0-9]):([0-5][0-9])(A|(?:AM)|P|(?:PM))?$', 'i'); |
||||
|
private _unsubscribeAll: Subject<any> = new Subject<any>(); |
||||
|
|
||||
|
/** |
||||
|
* Constructor |
||||
|
*/ |
||||
|
constructor( |
||||
|
private _changeDetectorRef: ChangeDetectorRef, |
||||
|
private _elementRef: ElementRef, |
||||
|
private _overlay: Overlay, |
||||
|
private _renderer2: Renderer2, |
||||
|
private _viewContainerRef: ViewContainerRef |
||||
|
) |
||||
|
{ |
||||
|
this._onChange = (): void => { |
||||
|
}; |
||||
|
this._onTouched = (): void => { |
||||
|
}; |
||||
|
this.dateFormat = 'DD/MM/YYYY'; |
||||
|
this.timeFormat = '12'; |
||||
|
|
||||
|
// Initialize the component
|
||||
|
this._init(); |
||||
|
} |
||||
|
|
||||
|
// -----------------------------------------------------------------------------------------------------
|
||||
|
// @ Accessors
|
||||
|
// -----------------------------------------------------------------------------------------------------
|
||||
|
|
||||
|
/** |
||||
|
* Setter & getter for dateFormat input |
||||
|
* |
||||
|
* @param value |
||||
|
*/ |
||||
|
@Input() |
||||
|
set dateFormat(value: string) |
||||
|
{ |
||||
|
// Return if the values are the same
|
||||
|
if ( this._dateFormat === value ) |
||||
|
{ |
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
// Store the value
|
||||
|
this._dateFormat = value; |
||||
|
} |
||||
|
|
||||
|
get dateFormat(): string |
||||
|
{ |
||||
|
return this._dateFormat; |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* Setter & getter for timeFormat input |
||||
|
* |
||||
|
* @param value |
||||
|
*/ |
||||
|
@Input() |
||||
|
set timeFormat(value: string) |
||||
|
{ |
||||
|
// Return if the values are the same
|
||||
|
if ( this._timeFormat === value ) |
||||
|
{ |
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
// Set format based on the time format input
|
||||
|
this._timeFormat = value === '12' ? 'hh:mmA' : 'HH:mm'; |
||||
|
} |
||||
|
|
||||
|
get timeFormat(): string |
||||
|
{ |
||||
|
return this._timeFormat; |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* Setter & getter for timeRange input |
||||
|
* |
||||
|
* @param value |
||||
|
*/ |
||||
|
@Input() |
||||
|
set timeRange(value: boolean) |
||||
|
{ |
||||
|
// Return if the values are the same
|
||||
|
if ( this._timeRange === value ) |
||||
|
{ |
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
// Store the value
|
||||
|
this._timeRange = value; |
||||
|
|
||||
|
// If the time range turned off...
|
||||
|
if ( !value ) |
||||
|
{ |
||||
|
this.range = { |
||||
|
start: this._range.start.clone().startOf('day'), |
||||
|
end : this._range.end.clone().endOf('day') |
||||
|
}; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
get timeRange(): boolean |
||||
|
{ |
||||
|
return this._timeRange; |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* Setter & getter for range input |
||||
|
* |
||||
|
* @param value |
||||
|
*/ |
||||
|
@Input() |
||||
|
set range(value) |
||||
|
{ |
||||
|
if ( !value ) |
||||
|
{ |
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
// Check if the value is an object and has 'start' and 'end' values
|
||||
|
if ( !value.start || !value.end ) |
||||
|
{ |
||||
|
console.error('Range input must have "start" and "end" properties!'); |
||||
|
|
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
// Check if we are setting an individual date or both of them
|
||||
|
const whichDate = value.whichDate || null; |
||||
|
|
||||
|
// Get the start and end dates as moment
|
||||
|
const start = moment(value.start); |
||||
|
const end = moment(value.end); |
||||
|
|
||||
|
// If we are only setting the start date...
|
||||
|
if ( whichDate === 'start' ) |
||||
|
{ |
||||
|
// Set the start date
|
||||
|
this._range.start = start.clone(); |
||||
|
|
||||
|
// If the selected start date is after the end date...
|
||||
|
if ( this._range.start.isAfter(this._range.end) ) |
||||
|
{ |
||||
|
// Set the end date to the start date but keep the end date's time
|
||||
|
const endDate = start.clone().hours(this._range.end.hours()).minutes(this._range.end.minutes()).seconds(this._range.end.seconds()); |
||||
|
|
||||
|
// Test this new end date to see if it's ahead of the start date
|
||||
|
if ( this._range.start.isBefore(endDate) ) |
||||
|
{ |
||||
|
// If it's, set the new end date
|
||||
|
this._range.end = endDate; |
||||
|
} |
||||
|
else |
||||
|
{ |
||||
|
// Otherwise, set the end date same as the start date
|
||||
|
this._range.end = start.clone(); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
// If we are only setting the end date...
|
||||
|
if ( whichDate === 'end' ) |
||||
|
{ |
||||
|
// Set the end date
|
||||
|
this._range.end = end.clone(); |
||||
|
|
||||
|
// If the selected end date is before the start date...
|
||||
|
if ( this._range.start.isAfter(this._range.end) ) |
||||
|
{ |
||||
|
// Set the start date to the end date but keep the start date's time
|
||||
|
const startDate = end.clone().hours(this._range.start.hours()).minutes(this._range.start.minutes()).seconds(this._range.start.seconds()); |
||||
|
|
||||
|
// Test this new end date to see if it's ahead of the start date
|
||||
|
if ( this._range.end.isAfter(startDate) ) |
||||
|
{ |
||||
|
// If it's, set the new start date
|
||||
|
this._range.start = startDate; |
||||
|
} |
||||
|
else |
||||
|
{ |
||||
|
// Otherwise, set the start date same as the end date
|
||||
|
this._range.start = end.clone(); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
// If we are setting both dates...
|
||||
|
if ( !whichDate ) |
||||
|
{ |
||||
|
// Set the start date
|
||||
|
this._range.start = start.clone(); |
||||
|
|
||||
|
// If the start date is before the end date, set the end date as normal.
|
||||
|
// If the start date is after the end date, set the end date same as the start date.
|
||||
|
this._range.end = start.isBefore(end) ? end.clone() : start.clone(); |
||||
|
} |
||||
|
|
||||
|
// Prepare another range object that holds the ISO formatted range dates
|
||||
|
const range = { |
||||
|
start: this._range.start.clone().toISOString(), |
||||
|
end : this._range.end.clone().toISOString() |
||||
|
}; |
||||
|
|
||||
|
// Emit the range changed event with the range
|
||||
|
this.rangeChanged.emit(range); |
||||
|
|
||||
|
// Update the model with the range if the change was not a programmatic change
|
||||
|
// Because programmatic changes trigger writeValue which triggers onChange and onTouched
|
||||
|
// internally causing them to trigger twice which breaks the form's pristine and touched
|
||||
|
// statuses.
|
||||
|
if ( !this._programmaticChange ) |
||||
|
{ |
||||
|
this._onTouched(range); |
||||
|
this._onChange(range); |
||||
|
} |
||||
|
|
||||
|
// Set the active dates
|
||||
|
this.activeDates = { |
||||
|
month1: this._range.start.clone(), |
||||
|
month2: this._range.start.clone().add(1, 'month') |
||||
|
}; |
||||
|
|
||||
|
// Set the time form controls
|
||||
|
this.startTimeFormControl.setValue(this._range.start.clone().format(this._timeFormat).toString()); |
||||
|
this.endTimeFormControl.setValue(this._range.end.clone().format(this._timeFormat).toString()); |
||||
|
|
||||
|
// Run ngAfterContentInit on month views to trigger
|
||||
|
// re-render on month views if they are available
|
||||
|
if ( this._matMonthView1 && this._matMonthView2 ) |
||||
|
{ |
||||
|
this._matMonthView1.ngAfterContentInit(); |
||||
|
this._matMonthView2.ngAfterContentInit(); |
||||
|
} |
||||
|
|
||||
|
// Reset the programmatic change status
|
||||
|
this._programmaticChange = false; |
||||
|
} |
||||
|
|
||||
|
get range(): any |
||||
|
{ |
||||
|
// Clone the range start and end
|
||||
|
const start = this._range.start.clone(); |
||||
|
const end = this._range.end.clone(); |
||||
|
|
||||
|
// Build and return the range object
|
||||
|
return { |
||||
|
startDate: start.clone().format(this.dateFormat), |
||||
|
startTime: this.timeRange ? start.clone().format(this.timeFormat) : null, |
||||
|
endDate : end.clone().format(this.dateFormat), |
||||
|
endTime : this.timeRange ? end.clone().format(this.timeFormat) : null |
||||
|
}; |
||||
|
} |
||||
|
|
||||
|
// -----------------------------------------------------------------------------------------------------
|
||||
|
// @ Control Value Accessor
|
||||
|
// -----------------------------------------------------------------------------------------------------
|
||||
|
|
||||
|
/** |
||||
|
* Update the form model on change |
||||
|
* |
||||
|
* @param fn |
||||
|
*/ |
||||
|
registerOnChange(fn: any): void |
||||
|
{ |
||||
|
this._onChange = fn; |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* Update the form model on blur |
||||
|
* |
||||
|
* @param fn |
||||
|
*/ |
||||
|
registerOnTouched(fn: any): void |
||||
|
{ |
||||
|
this._onTouched = fn; |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* Write to view from model when the form model changes programmatically |
||||
|
* |
||||
|
* @param range |
||||
|
*/ |
||||
|
writeValue(range: { start: string; end: string }): void |
||||
|
{ |
||||
|
// Set this change as a programmatic one
|
||||
|
this._programmaticChange = true; |
||||
|
|
||||
|
// Set the range
|
||||
|
this.range = range; |
||||
|
} |
||||
|
|
||||
|
// -----------------------------------------------------------------------------------------------------
|
||||
|
// @ Lifecycle hooks
|
||||
|
// -----------------------------------------------------------------------------------------------------
|
||||
|
|
||||
|
/** |
||||
|
* On init |
||||
|
*/ |
||||
|
ngOnInit(): void |
||||
|
{ |
||||
|
|
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* On destroy |
||||
|
*/ |
||||
|
ngOnDestroy(): void |
||||
|
{ |
||||
|
// Unsubscribe from all subscriptions
|
||||
|
this._unsubscribeAll.next(); |
||||
|
this._unsubscribeAll.complete(); |
||||
|
|
||||
|
// @ TODO: Workaround until "angular/issues/20007" resolved
|
||||
|
this.writeValue = (): void => { |
||||
|
}; |
||||
|
} |
||||
|
|
||||
|
// -----------------------------------------------------------------------------------------------------
|
||||
|
// @ Public methods
|
||||
|
// -----------------------------------------------------------------------------------------------------
|
||||
|
|
||||
|
/** |
||||
|
* Open the picker panel |
||||
|
*/ |
||||
|
openPickerPanel(): void |
||||
|
{ |
||||
|
// Create the overlay
|
||||
|
const overlayRef = this._overlay.create({ |
||||
|
panelClass : 'fuse-date-range-panel', |
||||
|
backdropClass : '', |
||||
|
hasBackdrop : true, |
||||
|
scrollStrategy : this._overlay.scrollStrategies.reposition(), |
||||
|
positionStrategy: this._overlay.position() |
||||
|
.flexibleConnectedTo(this._pickerPanelOrigin) |
||||
|
.withPositions([ |
||||
|
{ |
||||
|
originX : 'start', |
||||
|
originY : 'bottom', |
||||
|
overlayX: 'start', |
||||
|
overlayY: 'top', |
||||
|
offsetY : 8 |
||||
|
}, |
||||
|
{ |
||||
|
originX : 'start', |
||||
|
originY : 'top', |
||||
|
overlayX: 'start', |
||||
|
overlayY: 'bottom', |
||||
|
offsetY : -8 |
||||
|
} |
||||
|
]) |
||||
|
}); |
||||
|
|
||||
|
// Create a portal from the template
|
||||
|
const templatePortal = new TemplatePortal(this._pickerPanel, this._viewContainerRef); |
||||
|
|
||||
|
// On backdrop click
|
||||
|
overlayRef.backdropClick().subscribe(() => { |
||||
|
|
||||
|
// If template portal exists and attached...
|
||||
|
if ( templatePortal && templatePortal.isAttached ) |
||||
|
{ |
||||
|
// Detach it
|
||||
|
templatePortal.detach(); |
||||
|
} |
||||
|
|
||||
|
// If overlay exists and attached...
|
||||
|
if ( overlayRef && overlayRef.hasAttached() ) |
||||
|
{ |
||||
|
// Detach it
|
||||
|
overlayRef.detach(); |
||||
|
overlayRef.dispose(); |
||||
|
} |
||||
|
}); |
||||
|
|
||||
|
// Attach the portal to the overlay
|
||||
|
overlayRef.attach(templatePortal); |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* Get month label |
||||
|
* |
||||
|
* @param month |
||||
|
*/ |
||||
|
getMonthLabel(month: number): string |
||||
|
{ |
||||
|
if ( month === 1 ) |
||||
|
{ |
||||
|
return this.activeDates.month1.clone().format('MMMM Y'); |
||||
|
} |
||||
|
|
||||
|
return this.activeDates.month2.clone().format('MMMM Y'); |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* Date class function to add/remove class names to calendar days |
||||
|
*/ |
||||
|
dateClass(): any |
||||
|
{ |
||||
|
return (date: Moment): MatCalendarCellCssClasses => { |
||||
|
|
||||
|
// If the date is both start and end date...
|
||||
|
if ( date.isSame(this._range.start, 'day') && date.isSame(this._range.end, 'day') ) |
||||
|
{ |
||||
|
return ['fuse-date-range', 'fuse-date-range-start', 'fuse-date-range-end']; |
||||
|
} |
||||
|
|
||||
|
// If the date is the start date...
|
||||
|
if ( date.isSame(this._range.start, 'day') ) |
||||
|
{ |
||||
|
return ['fuse-date-range', 'fuse-date-range-start']; |
||||
|
} |
||||
|
|
||||
|
// If the date is the end date...
|
||||
|
if ( date.isSame(this._range.end, 'day') ) |
||||
|
{ |
||||
|
return ['fuse-date-range', 'fuse-date-range-end']; |
||||
|
} |
||||
|
|
||||
|
// If the date is in between start and end dates...
|
||||
|
if ( date.isBetween(this._range.start, this._range.end, 'day') ) |
||||
|
{ |
||||
|
return ['fuse-date-range', 'fuse-date-range-mid']; |
||||
|
} |
||||
|
|
||||
|
return undefined; |
||||
|
}; |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* Date filter to enable/disable calendar days |
||||
|
*/ |
||||
|
dateFilter(): any |
||||
|
{ |
||||
|
// If we are selecting the end date, disable all the dates that comes before the start date
|
||||
|
return (date: Moment): boolean => !(this.setWhichDate === 'end' && date.isBefore(this._range.start, 'day')); |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* On selected date change |
||||
|
* |
||||
|
* @param date |
||||
|
*/ |
||||
|
onSelectedDateChange(date: Moment): void |
||||
|
{ |
||||
|
// Create a new range object
|
||||
|
const newRange = { |
||||
|
start : this._range.start.clone().toISOString(), |
||||
|
end : this._range.end.clone().toISOString(), |
||||
|
whichDate: null |
||||
|
}; |
||||
|
|
||||
|
// Replace either the start or the end date with the new one
|
||||
|
// depending on which date we are setting
|
||||
|
if ( this.setWhichDate === 'start' ) |
||||
|
{ |
||||
|
newRange.start = moment(newRange.start).year(date.year()).month(date.month()).date(date.date()).toISOString(); |
||||
|
} |
||||
|
else |
||||
|
{ |
||||
|
newRange.end = moment(newRange.end).year(date.year()).month(date.month()).date(date.date()).toISOString(); |
||||
|
} |
||||
|
|
||||
|
// Append the which date to the new range object
|
||||
|
newRange.whichDate = this.setWhichDate; |
||||
|
|
||||
|
// Switch which date to set on the next run
|
||||
|
this.setWhichDate = this.setWhichDate === 'start' ? 'end' : 'start'; |
||||
|
|
||||
|
// Set the range
|
||||
|
this.range = newRange; |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* Go to previous month on both views |
||||
|
*/ |
||||
|
prev(): void |
||||
|
{ |
||||
|
this.activeDates.month1 = moment(this.activeDates.month1).subtract(1, 'month'); |
||||
|
this.activeDates.month2 = moment(this.activeDates.month2).subtract(1, 'month'); |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* Go to next month on both views |
||||
|
*/ |
||||
|
next(): void |
||||
|
{ |
||||
|
this.activeDates.month1 = moment(this.activeDates.month1).add(1, 'month'); |
||||
|
this.activeDates.month2 = moment(this.activeDates.month2).add(1, 'month'); |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* Update the start time |
||||
|
* |
||||
|
* @param event |
||||
|
*/ |
||||
|
updateStartTime(event): void |
||||
|
{ |
||||
|
// Parse the time
|
||||
|
const parsedTime = this._parseTime(event.target.value); |
||||
|
|
||||
|
// Go back to the previous value if the form control is not valid
|
||||
|
if ( this.startTimeFormControl.invalid ) |
||||
|
{ |
||||
|
// Override the time
|
||||
|
const time = this._range.start.clone().format(this._timeFormat); |
||||
|
|
||||
|
// Set the time
|
||||
|
this.startTimeFormControl.setValue(time); |
||||
|
|
||||
|
// Do not update the range
|
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
// Append the new time to the start date
|
||||
|
const startDate = this._range.start.clone().hours(parsedTime.hours()).minutes(parsedTime.minutes()); |
||||
|
|
||||
|
// If the new start date is after the current end date,
|
||||
|
// use the end date's time and set the start date again
|
||||
|
if ( startDate.isAfter(this._range.end) ) |
||||
|
{ |
||||
|
const endDateHours = this._range.end.hours(); |
||||
|
const endDateMinutes = this._range.end.minutes(); |
||||
|
|
||||
|
// Set the start date
|
||||
|
startDate.hours(endDateHours).minutes(endDateMinutes); |
||||
|
} |
||||
|
|
||||
|
// If everything is okay, set the new date
|
||||
|
this.range = { |
||||
|
start : startDate.toISOString(), |
||||
|
end : this._range.end.clone().toISOString(), |
||||
|
whichDate: 'start' |
||||
|
}; |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* Update the end time |
||||
|
* |
||||
|
* @param event |
||||
|
*/ |
||||
|
updateEndTime(event): void |
||||
|
{ |
||||
|
// Parse the time
|
||||
|
const parsedTime = this._parseTime(event.target.value); |
||||
|
|
||||
|
// Go back to the previous value if the form control is not valid
|
||||
|
if ( this.endTimeFormControl.invalid ) |
||||
|
{ |
||||
|
// Override the time
|
||||
|
const time = this._range.end.clone().format(this._timeFormat); |
||||
|
|
||||
|
// Set the time
|
||||
|
this.endTimeFormControl.setValue(time); |
||||
|
|
||||
|
// Do not update the range
|
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
// Append the new time to the end date
|
||||
|
const endDate = this._range.end.clone().hours(parsedTime.hours()).minutes(parsedTime.minutes()); |
||||
|
|
||||
|
// If the new end date is before the current start date,
|
||||
|
// use the start date's time and set the end date again
|
||||
|
if ( endDate.isBefore(this._range.start) ) |
||||
|
{ |
||||
|
const startDateHours = this._range.start.hours(); |
||||
|
const startDateMinutes = this._range.start.minutes(); |
||||
|
|
||||
|
// Set the end date
|
||||
|
endDate.hours(startDateHours).minutes(startDateMinutes); |
||||
|
} |
||||
|
|
||||
|
// If everything is okay, set the new date
|
||||
|
this.range = { |
||||
|
start : this._range.start.clone().toISOString(), |
||||
|
end : endDate.toISOString(), |
||||
|
whichDate: 'end' |
||||
|
}; |
||||
|
} |
||||
|
|
||||
|
// -----------------------------------------------------------------------------------------------------
|
||||
|
// @ Private methods
|
||||
|
// -----------------------------------------------------------------------------------------------------
|
||||
|
|
||||
|
/** |
||||
|
* Initialize |
||||
|
* |
||||
|
* @private |
||||
|
*/ |
||||
|
private _init(): void |
||||
|
{ |
||||
|
// Start and end time form controls
|
||||
|
this.startTimeFormControl = new FormControl('', [Validators.pattern(this._timeRegExp)]); |
||||
|
this.endTimeFormControl = new FormControl('', [Validators.pattern(this._timeRegExp)]); |
||||
|
|
||||
|
// Set the default range
|
||||
|
this._programmaticChange = true; |
||||
|
this.range = { |
||||
|
start: moment().startOf('day').toISOString(), |
||||
|
end : moment().add(1, 'day').endOf('day').toISOString() |
||||
|
}; |
||||
|
|
||||
|
// Set the default time range
|
||||
|
this._programmaticChange = true; |
||||
|
this.timeRange = true; |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* Parse the time from the inputs |
||||
|
* |
||||
|
* @param value |
||||
|
* @private |
||||
|
*/ |
||||
|
private _parseTime(value: string): Moment |
||||
|
{ |
||||
|
// Parse the time using the time regexp
|
||||
|
const timeArr = value.split(this._timeRegExp).filter(part => part !== ''); |
||||
|
|
||||
|
// Get the meridiem
|
||||
|
const meridiem = timeArr[2] || null; |
||||
|
|
||||
|
// If meridiem exists...
|
||||
|
if ( meridiem ) |
||||
|
{ |
||||
|
// Create a moment using 12-hours format and return it
|
||||
|
return moment(value, 'hh:mmA').seconds(0); |
||||
|
} |
||||
|
|
||||
|
// If meridiem doesn't exist, create a moment using 24-hours format and return in
|
||||
|
return moment(value, 'HH:mm').seconds(0); |
||||
|
} |
||||
|
} |
@ -0,0 +1,32 @@ |
|||||
|
import { NgModule } from '@angular/core'; |
||||
|
import { CommonModule } from '@angular/common'; |
||||
|
import { ReactiveFormsModule } from '@angular/forms'; |
||||
|
import { MatButtonModule } from '@angular/material/button'; |
||||
|
import { MatDatepickerModule } from '@angular/material/datepicker'; |
||||
|
import { MatFormFieldModule } from '@angular/material/form-field'; |
||||
|
import { MatIconModule } from '@angular/material/icon'; |
||||
|
import { MatInputModule } from '@angular/material/input'; |
||||
|
import { MatMomentDateModule } from '@angular/material-moment-adapter'; |
||||
|
import { FuseDateRangeComponent } from '@fuse/components/date-range/date-range.component'; |
||||
|
|
||||
|
@NgModule({ |
||||
|
declarations: [ |
||||
|
FuseDateRangeComponent |
||||
|
], |
||||
|
imports : [ |
||||
|
CommonModule, |
||||
|
ReactiveFormsModule, |
||||
|
MatButtonModule, |
||||
|
MatDatepickerModule, |
||||
|
MatFormFieldModule, |
||||
|
MatInputModule, |
||||
|
MatIconModule, |
||||
|
MatMomentDateModule |
||||
|
], |
||||
|
exports : [ |
||||
|
FuseDateRangeComponent |
||||
|
] |
||||
|
}) |
||||
|
export class FuseDateRangeModule |
||||
|
{ |
||||
|
} |
@ -0,0 +1 @@ |
|||||
|
export * from '@fuse/components/date-range/public-api'; |
@ -0,0 +1,2 @@ |
|||||
|
export * from '@fuse/components/date-range/date-range.component'; |
||||
|
export * from '@fuse/components/date-range/date-range.module'; |
@ -0,0 +1,3 @@ |
|||||
|
<div class="fuse-drawer-content"> |
||||
|
<ng-content></ng-content> |
||||
|
</div> |
@ -0,0 +1,131 @@ |
|||||
|
/* Variables */ |
||||
|
$fuse-drawer-width: 320; |
||||
|
|
||||
|
fuse-drawer { |
||||
|
position: relative; |
||||
|
display: flex; |
||||
|
flex-direction: column; |
||||
|
flex: 1 1 auto; |
||||
|
width: #{$fuse-drawer-width}px; |
||||
|
min-width: #{$fuse-drawer-width}px; |
||||
|
max-width: #{$fuse-drawer-width}px; |
||||
|
z-index: 300; |
||||
|
box-shadow: 0 2px 8px 0 rgba(0, 0, 0, .35); |
||||
|
@apply bg-card; |
||||
|
|
||||
|
/* Animations */ |
||||
|
&.fuse-drawer-animations-enabled { |
||||
|
transition-duration: 400ms; |
||||
|
transition-timing-function: cubic-bezier(0.25, 0.8, 0.25, 1); |
||||
|
transition-property: visibility, margin-left, margin-right, transform, width, max-width, min-width; |
||||
|
|
||||
|
.fuse-drawer-content { |
||||
|
transition-duration: 400ms; |
||||
|
transition-timing-function: cubic-bezier(0.25, 0.8, 0.25, 1); |
||||
|
transition-property: width, max-width, min-width; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/* Over mode */ |
||||
|
&.fuse-drawer-mode-over { |
||||
|
position: absolute; |
||||
|
top: 0; |
||||
|
bottom: 0; |
||||
|
|
||||
|
/* Fixed mode */ |
||||
|
&.fuse-drawer-fixed { |
||||
|
position: fixed; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/* Left position */ |
||||
|
&.fuse-drawer-position-left { |
||||
|
|
||||
|
/* Side mode */ |
||||
|
&.fuse-drawer-mode-side { |
||||
|
margin-left: -#{$fuse-drawer-width}px; |
||||
|
|
||||
|
&.fuse-drawer-opened { |
||||
|
margin-left: 0; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/* Over mode */ |
||||
|
&.fuse-drawer-mode-over { |
||||
|
left: 0; |
||||
|
transform: translate3d(-100%, 0, 0); |
||||
|
|
||||
|
&.fuse-drawer-opened { |
||||
|
transform: translate3d(0, 0, 0); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/* Content */ |
||||
|
.fuse-drawer-content { |
||||
|
left: 0; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/* Right position */ |
||||
|
&.fuse-drawer-position-right { |
||||
|
|
||||
|
/* Side mode */ |
||||
|
&.fuse-drawer-mode-side { |
||||
|
margin-right: -#{$fuse-drawer-width}px; |
||||
|
|
||||
|
&.fuse-drawer-opened { |
||||
|
margin-right: 0; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/* Over mode */ |
||||
|
&.fuse-drawer-mode-over { |
||||
|
right: 0; |
||||
|
transform: translate3d(100%, 0, 0); |
||||
|
|
||||
|
&.fuse-drawer-opened { |
||||
|
transform: translate3d(0, 0, 0); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/* Content */ |
||||
|
.fuse-drawer-content { |
||||
|
right: 0; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/* Content */ |
||||
|
.fuse-drawer-content { |
||||
|
position: absolute; |
||||
|
display: flex; |
||||
|
flex: 1 1 auto; |
||||
|
top: 0; |
||||
|
bottom: 0; |
||||
|
width: 100%; |
||||
|
height: 100%; |
||||
|
overflow: hidden; |
||||
|
@apply bg-card; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/* Overlay */ |
||||
|
.fuse-drawer-overlay { |
||||
|
position: absolute; |
||||
|
top: 0; |
||||
|
bottom: 0; |
||||
|
left: 0; |
||||
|
right: 0; |
||||
|
z-index: 299; |
||||
|
opacity: 1; |
||||
|
background-color: rgba(0, 0, 0, 0.6); |
||||
|
|
||||
|
/* Fixed mode */ |
||||
|
&.fuse-drawer-overlay-fixed { |
||||
|
position: fixed; |
||||
|
} |
||||
|
|
||||
|
/* Transparent overlay */ |
||||
|
&.fuse-drawer-overlay-transparent { |
||||
|
background-color: transparent; |
||||
|
} |
||||
|
} |
@ -0,0 +1,437 @@ |
|||||
|
import { Component, ElementRef, EventEmitter, HostBinding, HostListener, Input, OnChanges, OnDestroy, OnInit, Output, Renderer2, SimpleChanges, ViewEncapsulation } from '@angular/core'; |
||||
|
import { animate, AnimationBuilder, AnimationPlayer, style } from '@angular/animations'; |
||||
|
import { FuseDrawerMode, FuseDrawerPosition } from '@fuse/components/drawer/drawer.types'; |
||||
|
import { FuseDrawerService } from '@fuse/components/drawer/drawer.service'; |
||||
|
import { FuseUtilsService } from '@fuse/services/utils/utils.service'; |
||||
|
import { BooleanInput, coerceBooleanProperty } from '@angular/cdk/coercion'; |
||||
|
|
||||
|
@Component({ |
||||
|
selector : 'fuse-drawer', |
||||
|
templateUrl : './drawer.component.html', |
||||
|
styleUrls : ['./drawer.component.scss'], |
||||
|
encapsulation: ViewEncapsulation.None, |
||||
|
exportAs : 'fuseDrawer' |
||||
|
}) |
||||
|
export class FuseDrawerComponent implements OnChanges, OnInit, OnDestroy |
||||
|
{ |
||||
|
/* eslint-disable @typescript-eslint/naming-convention */ |
||||
|
static ngAcceptInputType_fixed: BooleanInput; |
||||
|
static ngAcceptInputType_opened: BooleanInput; |
||||
|
static ngAcceptInputType_transparentOverlay: BooleanInput; |
||||
|
/* eslint-enable @typescript-eslint/naming-convention */ |
||||
|
|
||||
|
@Input() fixed: boolean = false; |
||||
|
@Input() mode: FuseDrawerMode = 'side'; |
||||
|
@Input() name: string = this._fuseUtilsService.randomId(); |
||||
|
@Input() opened: boolean = false; |
||||
|
@Input() position: FuseDrawerPosition = 'left'; |
||||
|
@Input() transparentOverlay: boolean = false; |
||||
|
@Output() readonly fixedChanged: EventEmitter<boolean> = new EventEmitter<boolean>(); |
||||
|
@Output() readonly modeChanged: EventEmitter<FuseDrawerMode> = new EventEmitter<FuseDrawerMode>(); |
||||
|
@Output() readonly openedChanged: EventEmitter<boolean> = new EventEmitter<boolean>(); |
||||
|
@Output() readonly positionChanged: EventEmitter<FuseDrawerPosition> = new EventEmitter<FuseDrawerPosition>(); |
||||
|
|
||||
|
private _animationsEnabled: boolean = false; |
||||
|
private _hovered: boolean = false; |
||||
|
private _overlay: HTMLElement; |
||||
|
private _player: AnimationPlayer; |
||||
|
|
||||
|
/** |
||||
|
* Constructor |
||||
|
*/ |
||||
|
constructor( |
||||
|
private _animationBuilder: AnimationBuilder, |
||||
|
private _elementRef: ElementRef, |
||||
|
private _renderer2: Renderer2, |
||||
|
private _fuseDrawerService: FuseDrawerService, |
||||
|
private _fuseUtilsService: FuseUtilsService |
||||
|
) |
||||
|
{ |
||||
|
} |
||||
|
|
||||
|
// -----------------------------------------------------------------------------------------------------
|
||||
|
// @ Accessors
|
||||
|
// -----------------------------------------------------------------------------------------------------
|
||||
|
|
||||
|
/** |
||||
|
* Host binding for component classes |
||||
|
*/ |
||||
|
@HostBinding('class') get classList(): any |
||||
|
{ |
||||
|
return { |
||||
|
'fuse-drawer-animations-enabled' : this._animationsEnabled, |
||||
|
'fuse-drawer-fixed' : this.fixed, |
||||
|
'fuse-drawer-hover' : this._hovered, |
||||
|
[`fuse-drawer-mode-${this.mode}`] : true, |
||||
|
'fuse-drawer-opened' : this.opened, |
||||
|
[`fuse-drawer-position-${this.position}`]: true |
||||
|
}; |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* Host binding for component inline styles |
||||
|
*/ |
||||
|
@HostBinding('style') get styleList(): any |
||||
|
{ |
||||
|
return { |
||||
|
'visibility': this.opened ? 'visible' : 'hidden' |
||||
|
}; |
||||
|
} |
||||
|
|
||||
|
// -----------------------------------------------------------------------------------------------------
|
||||
|
// @ Decorated methods
|
||||
|
// -----------------------------------------------------------------------------------------------------
|
||||
|
|
||||
|
/** |
||||
|
* On mouseenter |
||||
|
* |
||||
|
* @private |
||||
|
*/ |
||||
|
@HostListener('mouseenter') |
||||
|
private _onMouseenter(): void |
||||
|
{ |
||||
|
// Enable the animations
|
||||
|
this._enableAnimations(); |
||||
|
|
||||
|
// Set the hovered
|
||||
|
this._hovered = true; |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* On mouseleave |
||||
|
* |
||||
|
* @private |
||||
|
*/ |
||||
|
@HostListener('mouseleave') |
||||
|
private _onMouseleave(): void |
||||
|
{ |
||||
|
// Enable the animations
|
||||
|
this._enableAnimations(); |
||||
|
|
||||
|
// Set the hovered
|
||||
|
this._hovered = false; |
||||
|
} |
||||
|
|
||||
|
// -----------------------------------------------------------------------------------------------------
|
||||
|
// @ Lifecycle hooks
|
||||
|
// -----------------------------------------------------------------------------------------------------
|
||||
|
|
||||
|
/** |
||||
|
* On changes |
||||
|
* |
||||
|
* @param changes |
||||
|
*/ |
||||
|
ngOnChanges(changes: SimpleChanges): void |
||||
|
{ |
||||
|
// Fixed
|
||||
|
if ( 'fixed' in changes ) |
||||
|
{ |
||||
|
// Coerce the value to a boolean
|
||||
|
this.fixed = coerceBooleanProperty(changes.fixed.currentValue); |
||||
|
|
||||
|
// Execute the observable
|
||||
|
this.fixedChanged.next(this.fixed); |
||||
|
} |
||||
|
|
||||
|
// Mode
|
||||
|
if ( 'mode' in changes ) |
||||
|
{ |
||||
|
// Get the previous and current values
|
||||
|
const previousMode = changes.mode.previousValue; |
||||
|
const currentMode = changes.mode.currentValue; |
||||
|
|
||||
|
// Disable the animations
|
||||
|
this._disableAnimations(); |
||||
|
|
||||
|
// If the mode changes: 'over -> side'
|
||||
|
if ( previousMode === 'over' && currentMode === 'side' ) |
||||
|
{ |
||||
|
// Hide the overlay
|
||||
|
this._hideOverlay(); |
||||
|
} |
||||
|
|
||||
|
// If the mode changes: 'side -> over'
|
||||
|
if ( previousMode === 'side' && currentMode === 'over' ) |
||||
|
{ |
||||
|
// If the drawer is opened
|
||||
|
if ( this.opened ) |
||||
|
{ |
||||
|
// Show the overlay
|
||||
|
this._showOverlay(); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
// Execute the observable
|
||||
|
this.modeChanged.next(currentMode); |
||||
|
|
||||
|
// Enable the animations after a delay
|
||||
|
// The delay must be bigger than the current transition-duration
|
||||
|
// to make sure nothing will be animated while the mode is changing
|
||||
|
setTimeout(() => { |
||||
|
this._enableAnimations(); |
||||
|
}, 500); |
||||
|
} |
||||
|
|
||||
|
// Opened
|
||||
|
if ( 'opened' in changes ) |
||||
|
{ |
||||
|
// Coerce the value to a boolean
|
||||
|
const open = coerceBooleanProperty(changes.opened.currentValue); |
||||
|
|
||||
|
// Open/close the drawer
|
||||
|
this._toggleOpened(open); |
||||
|
} |
||||
|
|
||||
|
// Position
|
||||
|
if ( 'position' in changes ) |
||||
|
{ |
||||
|
// Execute the observable
|
||||
|
this.positionChanged.next(this.position); |
||||
|
} |
||||
|
|
||||
|
// Transparent overlay
|
||||
|
if ( 'transparentOverlay' in changes ) |
||||
|
{ |
||||
|
// Coerce the value to a boolean
|
||||
|
this.transparentOverlay = coerceBooleanProperty(changes.transparentOverlay.currentValue); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* On init |
||||
|
*/ |
||||
|
ngOnInit(): void |
||||
|
{ |
||||
|
// Register the drawer
|
||||
|
this._fuseDrawerService.registerComponent(this.name, this); |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* On destroy |
||||
|
*/ |
||||
|
ngOnDestroy(): void |
||||
|
{ |
||||
|
// Finish the animation
|
||||
|
if ( this._player ) |
||||
|
{ |
||||
|
this._player.finish(); |
||||
|
} |
||||
|
|
||||
|
// Deregister the drawer from the registry
|
||||
|
this._fuseDrawerService.deregisterComponent(this.name); |
||||
|
} |
||||
|
|
||||
|
// -----------------------------------------------------------------------------------------------------
|
||||
|
// @ Public methods
|
||||
|
// -----------------------------------------------------------------------------------------------------
|
||||
|
|
||||
|
/** |
||||
|
* Open the drawer |
||||
|
*/ |
||||
|
open(): void |
||||
|
{ |
||||
|
// Return if the drawer has already opened
|
||||
|
if ( this.opened ) |
||||
|
{ |
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
// Open the drawer
|
||||
|
this._toggleOpened(true); |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* Close the drawer |
||||
|
*/ |
||||
|
close(): void |
||||
|
{ |
||||
|
// Return if the drawer has already closed
|
||||
|
if ( !this.opened ) |
||||
|
{ |
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
// Close the drawer
|
||||
|
this._toggleOpened(false); |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* Toggle the drawer |
||||
|
*/ |
||||
|
toggle(): void |
||||
|
{ |
||||
|
if ( this.opened ) |
||||
|
{ |
||||
|
this.close(); |
||||
|
} |
||||
|
else |
||||
|
{ |
||||
|
this.open(); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
// -----------------------------------------------------------------------------------------------------
|
||||
|
// @ Private methods
|
||||
|
// -----------------------------------------------------------------------------------------------------
|
||||
|
|
||||
|
/** |
||||
|
* Enable the animations |
||||
|
* |
||||
|
* @private |
||||
|
*/ |
||||
|
private _enableAnimations(): void |
||||
|
{ |
||||
|
// Return if the animations are already enabled
|
||||
|
if ( this._animationsEnabled ) |
||||
|
{ |
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
// Enable the animations
|
||||
|
this._animationsEnabled = true; |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* Disable the animations |
||||
|
* |
||||
|
* @private |
||||
|
*/ |
||||
|
private _disableAnimations(): void |
||||
|
{ |
||||
|
// Return if the animations are already disabled
|
||||
|
if ( !this._animationsEnabled ) |
||||
|
{ |
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
// Disable the animations
|
||||
|
this._animationsEnabled = false; |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* Show the backdrop |
||||
|
* |
||||
|
* @private |
||||
|
*/ |
||||
|
private _showOverlay(): void |
||||
|
{ |
||||
|
// Create the backdrop element
|
||||
|
this._overlay = this._renderer2.createElement('div'); |
||||
|
|
||||
|
// Return if overlay couldn't be create for some reason
|
||||
|
if ( !this._overlay ) |
||||
|
{ |
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
// Add a class to the backdrop element
|
||||
|
this._overlay.classList.add('fuse-drawer-overlay'); |
||||
|
|
||||
|
// Add a class depending on the fixed option
|
||||
|
if ( this.fixed ) |
||||
|
{ |
||||
|
this._overlay.classList.add('fuse-drawer-overlay-fixed'); |
||||
|
} |
||||
|
|
||||
|
// Add a class depending on the transparentOverlay option
|
||||
|
if ( this.transparentOverlay ) |
||||
|
{ |
||||
|
this._overlay.classList.add('fuse-drawer-overlay-transparent'); |
||||
|
} |
||||
|
|
||||
|
// Append the backdrop to the parent of the drawer
|
||||
|
this._renderer2.appendChild(this._elementRef.nativeElement.parentElement, this._overlay); |
||||
|
|
||||
|
// Create the enter animation and attach it to the player
|
||||
|
this._player = this._animationBuilder.build([ |
||||
|
style({opacity: 0}), |
||||
|
animate('300ms cubic-bezier(0.25, 0.8, 0.25, 1)', style({opacity: 1})) |
||||
|
]).create(this._overlay); |
||||
|
|
||||
|
// Once the animation is done...
|
||||
|
this._player.onDone(() => { |
||||
|
|
||||
|
// Destroy the player
|
||||
|
this._player.destroy(); |
||||
|
this._player = null; |
||||
|
}); |
||||
|
|
||||
|
// Play the animation
|
||||
|
this._player.play(); |
||||
|
|
||||
|
// Add an event listener to the overlay
|
||||
|
this._overlay.addEventListener('click', () => { |
||||
|
this.close(); |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* Hide the backdrop |
||||
|
* |
||||
|
* @private |
||||
|
*/ |
||||
|
private _hideOverlay(): void |
||||
|
{ |
||||
|
if ( !this._overlay ) |
||||
|
{ |
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
// Create the leave animation and attach it to the player
|
||||
|
this._player = this._animationBuilder.build([ |
||||
|
animate('300ms cubic-bezier(0.25, 0.8, 0.25, 1)', style({opacity: 0})) |
||||
|
]).create(this._overlay); |
||||
|
|
||||
|
// Play the animation
|
||||
|
this._player.play(); |
||||
|
|
||||
|
// Once the animation is done...
|
||||
|
this._player.onDone(() => { |
||||
|
|
||||
|
// Destroy the player
|
||||
|
this._player.destroy(); |
||||
|
this._player = null; |
||||
|
|
||||
|
// If the backdrop still exists...
|
||||
|
if ( this._overlay ) |
||||
|
{ |
||||
|
// Remove the backdrop
|
||||
|
this._overlay.parentNode.removeChild(this._overlay); |
||||
|
this._overlay = null; |
||||
|
} |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* Open/close the drawer |
||||
|
* |
||||
|
* @param open |
||||
|
* @private |
||||
|
*/ |
||||
|
private _toggleOpened(open: boolean): void |
||||
|
{ |
||||
|
// Set the opened
|
||||
|
this.opened = open; |
||||
|
|
||||
|
// Enable the animations
|
||||
|
this._enableAnimations(); |
||||
|
|
||||
|
// If the mode is 'over'
|
||||
|
if ( this.mode === 'over' ) |
||||
|
{ |
||||
|
// If the drawer opens, show the overlay
|
||||
|
if ( open ) |
||||
|
{ |
||||
|
this._showOverlay(); |
||||
|
} |
||||
|
// Otherwise, close the overlay
|
||||
|
else |
||||
|
{ |
||||
|
this._hideOverlay(); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
// Execute the observable
|
||||
|
this.openedChanged.next(open); |
||||
|
} |
||||
|
} |
@ -0,0 +1,18 @@ |
|||||
|
import { NgModule } from '@angular/core'; |
||||
|
import { CommonModule } from '@angular/common'; |
||||
|
import { FuseDrawerComponent } from '@fuse/components/drawer/drawer.component'; |
||||
|
|
||||
|
@NgModule({ |
||||
|
declarations: [ |
||||
|
FuseDrawerComponent |
||||
|
], |
||||
|
imports : [ |
||||
|
CommonModule |
||||
|
], |
||||
|
exports : [ |
||||
|
FuseDrawerComponent |
||||
|
] |
||||
|
}) |
||||
|
export class FuseDrawerModule |
||||
|
{ |
||||
|
} |
@ -0,0 +1,52 @@ |
|||||
|
import { Injectable } from '@angular/core'; |
||||
|
import { FuseDrawerComponent } from '@fuse/components/drawer/drawer.component'; |
||||
|
|
||||
|
@Injectable({ |
||||
|
providedIn: 'root' |
||||
|
}) |
||||
|
export class FuseDrawerService |
||||
|
{ |
||||
|
private _componentRegistry: Map<string, FuseDrawerComponent> = new Map<string, FuseDrawerComponent>(); |
||||
|
|
||||
|
/** |
||||
|
* Constructor |
||||
|
*/ |
||||
|
constructor() |
||||
|
{ |
||||
|
} |
||||
|
|
||||
|
// -----------------------------------------------------------------------------------------------------
|
||||
|
// @ Public methods
|
||||
|
// -----------------------------------------------------------------------------------------------------
|
||||
|
|
||||
|
/** |
||||
|
* Register drawer component |
||||
|
* |
||||
|
* @param name |
||||
|
* @param component |
||||
|
*/ |
||||
|
registerComponent(name: string, component: FuseDrawerComponent): void |
||||
|
{ |
||||
|
this._componentRegistry.set(name, component); |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* Deregister drawer component |
||||
|
* |
||||
|
* @param name |
||||
|
*/ |
||||
|
deregisterComponent(name: string): void |
||||
|
{ |
||||
|
this._componentRegistry.delete(name); |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* Get drawer component from the registry |
||||
|
* |
||||
|
* @param name |
||||
|
*/ |
||||
|
getComponent(name: string): FuseDrawerComponent | undefined |
||||
|
{ |
||||
|
return this._componentRegistry.get(name); |
||||
|
} |
||||
|
} |
@ -0,0 +1,7 @@ |
|||||
|
export type FuseDrawerMode = |
||||
|
| 'over' |
||||
|
| 'side'; |
||||
|
|
||||
|
export type FuseDrawerPosition = |
||||
|
| 'left' |
||||
|
| 'right'; |
@ -0,0 +1 @@ |
|||||
|
export * from '@fuse/components/drawer/public-api'; |
@ -0,0 +1,4 @@ |
|||||
|
export * from '@fuse/components/drawer/drawer.component'; |
||||
|
export * from '@fuse/components/drawer/drawer.module'; |
||||
|
export * from '@fuse/components/drawer/drawer.service'; |
||||
|
export * from '@fuse/components/drawer/drawer.types'; |
@ -0,0 +1,12 @@ |
|||||
|
<!-- Button --> |
||||
|
<button |
||||
|
mat-icon-button |
||||
|
[matTooltip]="tooltip || 'Toggle Fullscreen'" |
||||
|
(click)="toggleFullscreen()"> |
||||
|
<ng-container [ngTemplateOutlet]="iconTpl || defaultIconTpl"></ng-container> |
||||
|
</button> |
||||
|
|
||||
|
<!-- Default icon --> |
||||
|
<ng-template #defaultIconTpl> |
||||
|
<mat-icon [svgIcon]="'heroicons_outline:arrows-expand'"></mat-icon> |
||||
|
</ng-template> |
@ -0,0 +1,166 @@ |
|||||
|
import { ChangeDetectionStrategy, Component, Inject, Input, OnInit, TemplateRef, ViewEncapsulation } from '@angular/core'; |
||||
|
import { DOCUMENT } from '@angular/common'; |
||||
|
import { FSDocument, FSDocumentElement } from '@fuse/components/fullscreen/fullscreen.types'; |
||||
|
|
||||
|
@Component({ |
||||
|
selector : 'fuse-fullscreen', |
||||
|
templateUrl : './fullscreen.component.html', |
||||
|
encapsulation : ViewEncapsulation.None, |
||||
|
changeDetection: ChangeDetectionStrategy.OnPush, |
||||
|
exportAs : 'fuseFullscreen' |
||||
|
}) |
||||
|
export class FuseFullscreenComponent implements OnInit |
||||
|
{ |
||||
|
@Input() iconTpl: TemplateRef<any>; |
||||
|
@Input() tooltip: string; |
||||
|
private _fsDoc: FSDocument; |
||||
|
private _fsDocEl: FSDocumentElement; |
||||
|
private _isFullscreen: boolean = false; |
||||
|
|
||||
|
/** |
||||
|
* Constructor |
||||
|
*/ |
||||
|
constructor(@Inject(DOCUMENT) private _document: Document) |
||||
|
{ |
||||
|
this._fsDoc = _document as FSDocument; |
||||
|
} |
||||
|
|
||||
|
// -----------------------------------------------------------------------------------------------------
|
||||
|
// @ Lifecycle hooks
|
||||
|
// -----------------------------------------------------------------------------------------------------
|
||||
|
|
||||
|
/** |
||||
|
* On init |
||||
|
*/ |
||||
|
ngOnInit(): void |
||||
|
{ |
||||
|
this._fsDocEl = document.documentElement as FSDocumentElement; |
||||
|
} |
||||
|
|
||||
|
// -----------------------------------------------------------------------------------------------------
|
||||
|
// @ Public methods
|
||||
|
// -----------------------------------------------------------------------------------------------------
|
||||
|
|
||||
|
/** |
||||
|
* Toggle the fullscreen mode |
||||
|
*/ |
||||
|
toggleFullscreen(): void |
||||
|
{ |
||||
|
// Check if the fullscreen is open
|
||||
|
this._isFullscreen = this._getBrowserFullscreenElement() !== null; |
||||
|
|
||||
|
// Toggle the fullscreen
|
||||
|
if ( this._isFullscreen ) |
||||
|
{ |
||||
|
this._closeFullscreen(); |
||||
|
} |
||||
|
else |
||||
|
{ |
||||
|
this._openFullscreen(); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
// -----------------------------------------------------------------------------------------------------
|
||||
|
// @ Private methods
|
||||
|
// -----------------------------------------------------------------------------------------------------
|
||||
|
|
||||
|
/** |
||||
|
* Get browser's fullscreen element |
||||
|
* |
||||
|
* @private |
||||
|
*/ |
||||
|
private _getBrowserFullscreenElement(): Element |
||||
|
{ |
||||
|
if ( typeof this._fsDoc.fullscreenElement !== 'undefined' ) |
||||
|
{ |
||||
|
return this._fsDoc.fullscreenElement; |
||||
|
} |
||||
|
|
||||
|
if ( typeof this._fsDoc.mozFullScreenElement !== 'undefined' ) |
||||
|
{ |
||||
|
return this._fsDoc.mozFullScreenElement; |
||||
|
} |
||||
|
|
||||
|
if ( typeof this._fsDoc.msFullscreenElement !== 'undefined' ) |
||||
|
{ |
||||
|
return this._fsDoc.msFullscreenElement; |
||||
|
} |
||||
|
|
||||
|
if ( typeof this._fsDoc.webkitFullscreenElement !== 'undefined' ) |
||||
|
{ |
||||
|
return this._fsDoc.webkitFullscreenElement; |
||||
|
} |
||||
|
|
||||
|
throw new Error('Fullscreen mode is not supported by this browser'); |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* Open the fullscreen |
||||
|
* |
||||
|
* @private |
||||
|
*/ |
||||
|
private _openFullscreen(): void |
||||
|
{ |
||||
|
if ( this._fsDocEl.requestFullscreen ) |
||||
|
{ |
||||
|
this._fsDocEl.requestFullscreen(); |
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
// Firefox
|
||||
|
if ( this._fsDocEl.mozRequestFullScreen ) |
||||
|
{ |
||||
|
this._fsDocEl.mozRequestFullScreen(); |
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
// Chrome, Safari and Opera
|
||||
|
if ( this._fsDocEl.webkitRequestFullscreen ) |
||||
|
{ |
||||
|
this._fsDocEl.webkitRequestFullscreen(); |
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
// IE/Edge
|
||||
|
if ( this._fsDocEl.msRequestFullscreen ) |
||||
|
{ |
||||
|
this._fsDocEl.msRequestFullscreen(); |
||||
|
return; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* Close the fullscreen |
||||
|
* |
||||
|
* @private |
||||
|
*/ |
||||
|
private _closeFullscreen(): void |
||||
|
{ |
||||
|
if ( this._fsDoc.exitFullscreen ) |
||||
|
{ |
||||
|
this._fsDoc.exitFullscreen(); |
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
// Firefox
|
||||
|
if ( this._fsDoc.mozCancelFullScreen ) |
||||
|
{ |
||||
|
this._fsDoc.mozCancelFullScreen(); |
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
// Chrome, Safari and Opera
|
||||
|
if ( this._fsDoc.webkitExitFullscreen ) |
||||
|
{ |
||||
|
this._fsDoc.webkitExitFullscreen(); |
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
// IE/Edge
|
||||
|
else if ( this._fsDoc.msExitFullscreen ) |
||||
|
{ |
||||
|
this._fsDoc.msExitFullscreen(); |
||||
|
return; |
||||
|
} |
||||
|
} |
||||
|
} |
@ -0,0 +1,24 @@ |
|||||
|
import { NgModule } from '@angular/core'; |
||||
|
import { MatButtonModule } from '@angular/material/button'; |
||||
|
import { MatIconModule } from '@angular/material/icon'; |
||||
|
import { MatTooltipModule } from '@angular/material/tooltip'; |
||||
|
import { FuseFullscreenComponent } from '@fuse/components/fullscreen/fullscreen.component'; |
||||
|
import { CommonModule } from '@angular/common'; |
||||
|
|
||||
|
@NgModule({ |
||||
|
declarations: [ |
||||
|
FuseFullscreenComponent |
||||
|
], |
||||
|
imports: [ |
||||
|
MatButtonModule, |
||||
|
MatIconModule, |
||||
|
MatTooltipModule, |
||||
|
CommonModule |
||||
|
], |
||||
|
exports : [ |
||||
|
FuseFullscreenComponent |
||||
|
] |
||||
|
}) |
||||
|
export class FuseFullscreenModule |
||||
|
{ |
||||
|
} |
@ -0,0 +1,16 @@ |
|||||
|
export interface FSDocument extends HTMLDocument |
||||
|
{ |
||||
|
mozFullScreenElement?: Element; |
||||
|
mozCancelFullScreen?: () => void; |
||||
|
msFullscreenElement?: Element; |
||||
|
msExitFullscreen?: () => void; |
||||
|
webkitFullscreenElement?: Element; |
||||
|
webkitExitFullscreen?: () => void; |
||||
|
} |
||||
|
|
||||
|
export interface FSDocumentElement extends HTMLElement |
||||
|
{ |
||||
|
mozRequestFullScreen?: () => void; |
||||
|
msRequestFullscreen?: () => void; |
||||
|
webkitRequestFullscreen?: () => void; |
||||
|
} |
@ -0,0 +1 @@ |
|||||
|
export * from '@fuse/components/fullscreen/public-api'; |
@ -0,0 +1,3 @@ |
|||||
|
export * from '@fuse/components/fullscreen/fullscreen.component'; |
||||
|
export * from '@fuse/components/fullscreen/fullscreen.module'; |
||||
|
export * from '@fuse/components/fullscreen/fullscreen.types'; |
@ -0,0 +1,9 @@ |
|||||
|
<ng-content></ng-content> |
||||
|
|
||||
|
<!-- @formatter:off --> |
||||
|
<ng-template let-highlightedCode="highlightedCode" let-lang="lang"> |
||||
|
<div class="fuse-highlight fuse-highlight-code-container"> |
||||
|
<pre [ngClass]="'language-' + lang"><code [ngClass]="'language-' + lang" [innerHTML]="highlightedCode"></code></pre> |
||||
|
</div> |
||||
|
</ng-template> |
||||
|
<!-- @formatter:on --> |
@ -0,0 +1,3 @@ |
|||||
|
textarea[fuse-highlight] { |
||||
|
display: none; |
||||
|
} |
@ -0,0 +1,132 @@ |
|||||
|
import { AfterViewInit, ChangeDetectionStrategy, ChangeDetectorRef, Component, ElementRef, EmbeddedViewRef, Input, OnChanges, Renderer2, SecurityContext, SimpleChanges, TemplateRef, ViewChild, ViewContainerRef, ViewEncapsulation } from '@angular/core'; |
||||
|
import { DomSanitizer } from '@angular/platform-browser'; |
||||
|
import { FuseHighlightService } from '@fuse/components/highlight/highlight.service'; |
||||
|
|
||||
|
@Component({ |
||||
|
selector : 'textarea[fuse-highlight]', |
||||
|
templateUrl : './highlight.component.html', |
||||
|
styleUrls : ['./highlight.component.scss'], |
||||
|
encapsulation : ViewEncapsulation.None, |
||||
|
changeDetection: ChangeDetectionStrategy.OnPush, |
||||
|
exportAs : 'fuseHighlight' |
||||
|
}) |
||||
|
export class FuseHighlightComponent implements OnChanges, AfterViewInit |
||||
|
{ |
||||
|
@Input() code: string; |
||||
|
@Input() lang: string; |
||||
|
@ViewChild(TemplateRef) templateRef: TemplateRef<any>; |
||||
|
|
||||
|
highlightedCode: string; |
||||
|
private _viewRef: EmbeddedViewRef<any>; |
||||
|
|
||||
|
/** |
||||
|
* Constructor |
||||
|
*/ |
||||
|
constructor( |
||||
|
private _changeDetectorRef: ChangeDetectorRef, |
||||
|
private _domSanitizer: DomSanitizer, |
||||
|
private _elementRef: ElementRef, |
||||
|
private _renderer2: Renderer2, |
||||
|
private _fuseHighlightService: FuseHighlightService, |
||||
|
private _viewContainerRef: ViewContainerRef |
||||
|
) |
||||
|
{ |
||||
|
} |
||||
|
|
||||
|
// -----------------------------------------------------------------------------------------------------
|
||||
|
// @ Lifecycle hooks
|
||||
|
// -----------------------------------------------------------------------------------------------------
|
||||
|
|
||||
|
/** |
||||
|
* On changes |
||||
|
* |
||||
|
* @param changes |
||||
|
*/ |
||||
|
ngOnChanges(changes: SimpleChanges): void |
||||
|
{ |
||||
|
// Code & Lang
|
||||
|
if ( 'code' in changes || 'lang' in changes ) |
||||
|
{ |
||||
|
// Return if the viewContainerRef is not available
|
||||
|
if ( !this._viewContainerRef.length ) |
||||
|
{ |
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
// Highlight and insert the code
|
||||
|
this._highlightAndInsert(); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* After view init |
||||
|
*/ |
||||
|
ngAfterViewInit(): void |
||||
|
{ |
||||
|
// Return if there is no language set
|
||||
|
if ( !this.lang ) |
||||
|
{ |
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
// If there is no code input, get the code from
|
||||
|
// the textarea
|
||||
|
if ( !this.code ) |
||||
|
{ |
||||
|
// Get the code
|
||||
|
this.code = this._elementRef.nativeElement.value; |
||||
|
} |
||||
|
|
||||
|
// Highlight and insert
|
||||
|
this._highlightAndInsert(); |
||||
|
} |
||||
|
|
||||
|
// -----------------------------------------------------------------------------------------------------
|
||||
|
// @ Private methods
|
||||
|
// -----------------------------------------------------------------------------------------------------
|
||||
|
|
||||
|
/** |
||||
|
* Highlight and insert the highlighted code |
||||
|
* |
||||
|
* @private |
||||
|
*/ |
||||
|
private _highlightAndInsert(): void |
||||
|
{ |
||||
|
// Return if the template reference is not available
|
||||
|
if ( !this.templateRef ) |
||||
|
{ |
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
// Return if the code or language is not defined
|
||||
|
if ( !this.code || !this.lang ) |
||||
|
{ |
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
// Destroy the component if there is already one
|
||||
|
if ( this._viewRef ) |
||||
|
{ |
||||
|
this._viewRef.destroy(); |
||||
|
this._viewRef = null; |
||||
|
} |
||||
|
|
||||
|
// Highlight and sanitize the code just in case
|
||||
|
this.highlightedCode = this._domSanitizer.sanitize(SecurityContext.HTML, this._fuseHighlightService.highlight(this.code, this.lang)); |
||||
|
|
||||
|
// Return if the highlighted code is null
|
||||
|
if ( this.highlightedCode === null ) |
||||
|
{ |
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
// Render and insert the template
|
||||
|
this._viewRef = this._viewContainerRef.createEmbeddedView(this.templateRef, { |
||||
|
highlightedCode: this.highlightedCode, |
||||
|
lang : this.lang |
||||
|
}); |
||||
|
|
||||
|
// Detect the changes
|
||||
|
this._viewRef.detectChanges(); |
||||
|
} |
||||
|
} |
@ -0,0 +1,18 @@ |
|||||
|
import { NgModule } from '@angular/core'; |
||||
|
import { CommonModule } from '@angular/common'; |
||||
|
import { FuseHighlightComponent } from '@fuse/components/highlight/highlight.component'; |
||||
|
|
||||
|
@NgModule({ |
||||
|
declarations: [ |
||||
|
FuseHighlightComponent |
||||
|
], |
||||
|
imports : [ |
||||
|
CommonModule |
||||
|
], |
||||
|
exports : [ |
||||
|
FuseHighlightComponent |
||||
|
] |
||||
|
}) |
||||
|
export class FuseHighlightModule |
||||
|
{ |
||||
|
} |
@ -0,0 +1,82 @@ |
|||||
|
import { Injectable } from '@angular/core'; |
||||
|
import hljs from 'highlight.js'; |
||||
|
|
||||
|
@Injectable({ |
||||
|
providedIn: 'root' |
||||
|
}) |
||||
|
export class FuseHighlightService |
||||
|
{ |
||||
|
/** |
||||
|
* Constructor |
||||
|
*/ |
||||
|
constructor() |
||||
|
{ |
||||
|
} |
||||
|
|
||||
|
// -----------------------------------------------------------------------------------------------------
|
||||
|
// @ Public methods
|
||||
|
// -----------------------------------------------------------------------------------------------------
|
||||
|
|
||||
|
/** |
||||
|
* Highlight |
||||
|
*/ |
||||
|
highlight(code: string, language: string): string |
||||
|
{ |
||||
|
// Format the code
|
||||
|
code = this._format(code); |
||||
|
|
||||
|
// Highlight and return the code
|
||||
|
return hljs.highlight(code, {language}).value; |
||||
|
} |
||||
|
|
||||
|
// -----------------------------------------------------------------------------------------------------
|
||||
|
// @ Private methods
|
||||
|
// -----------------------------------------------------------------------------------------------------
|
||||
|
|
||||
|
/** |
||||
|
* Remove the empty lines around the code block |
||||
|
* and re-align the indentation based on the first |
||||
|
* non-whitespace indented character |
||||
|
* |
||||
|
* @param code |
||||
|
* @private |
||||
|
*/ |
||||
|
private _format(code: string): string |
||||
|
{ |
||||
|
let indentation = 0; |
||||
|
|
||||
|
// Split the code into lines and store the lines
|
||||
|
const lines = code.split('\n'); |
||||
|
|
||||
|
// Trim the empty lines around the code block
|
||||
|
while ( lines.length && lines[0].trim() === '' ) |
||||
|
{ |
||||
|
lines.shift(); |
||||
|
} |
||||
|
|
||||
|
while ( lines.length && lines[lines.length - 1].trim() === '' ) |
||||
|
{ |
||||
|
lines.pop(); |
||||
|
} |
||||
|
|
||||
|
// Iterate through the lines
|
||||
|
lines.filter(line => line.length) |
||||
|
.forEach((line, index) => { |
||||
|
|
||||
|
// Always get the indentation of the first line so we can
|
||||
|
// have something to compare with
|
||||
|
if ( index === 0 ) |
||||
|
{ |
||||
|
indentation = line.search(/\S|$/); |
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
// Look at all the remaining lines to figure out the smallest indentation.
|
||||
|
indentation = Math.min(line.search(/\S|$/), indentation); |
||||
|
}); |
||||
|
|
||||
|
// Iterate through the lines one more time, remove the extra
|
||||
|
// indentation, join them together and return it
|
||||
|
return lines.map(line => line.substring(indentation)).join('\n'); |
||||
|
} |
||||
|
} |
@ -0,0 +1 @@ |
|||||
|
export * from '@fuse/components/highlight/public-api'; |
@ -0,0 +1,3 @@ |
|||||
|
export * from '@fuse/components/highlight/highlight.component'; |
||||
|
export * from '@fuse/components/highlight/highlight.module'; |
||||
|
export * from '@fuse/components/highlight/highlight.service'; |
@ -0,0 +1 @@ |
|||||
|
export * from '@fuse/components/masonry/public-api'; |
@ -0,0 +1,3 @@ |
|||||
|
<div class="flex"> |
||||
|
<ng-container *ngTemplateOutlet="columnsTemplate; context: { $implicit: distributedColumns }"></ng-container> |
||||
|
</div> |
@ -0,0 +1,87 @@ |
|||||
|
import { AfterViewInit, Component, Input, OnChanges, SimpleChanges, TemplateRef, ViewEncapsulation } from '@angular/core'; |
||||
|
import { fuseAnimations } from '@fuse/animations'; |
||||
|
import { FuseMediaWatcherService } from '@fuse/services/media-watcher'; |
||||
|
|
||||
|
@Component({ |
||||
|
selector : 'fuse-masonry', |
||||
|
templateUrl : './masonry.component.html', |
||||
|
styleUrls : ['./masonry.component.scss'], |
||||
|
encapsulation: ViewEncapsulation.None, |
||||
|
animations : fuseAnimations, |
||||
|
exportAs : 'fuseMasonry' |
||||
|
}) |
||||
|
export class FuseMasonryComponent implements OnChanges, AfterViewInit |
||||
|
{ |
||||
|
@Input() columnsTemplate: TemplateRef<any>; |
||||
|
@Input() columns: number; |
||||
|
@Input() items: any[] = []; |
||||
|
distributedColumns: any[] = []; |
||||
|
|
||||
|
/** |
||||
|
* Constructor |
||||
|
*/ |
||||
|
constructor(private _fuseMediaWatcherService: FuseMediaWatcherService) |
||||
|
{ |
||||
|
} |
||||
|
|
||||
|
// -----------------------------------------------------------------------------------------------------
|
||||
|
// @ Lifecycle hooks
|
||||
|
// -----------------------------------------------------------------------------------------------------
|
||||
|
|
||||
|
/** |
||||
|
* On changes |
||||
|
* |
||||
|
* @param changes |
||||
|
*/ |
||||
|
ngOnChanges(changes: SimpleChanges): void |
||||
|
{ |
||||
|
// Columns
|
||||
|
if ( 'columns' in changes ) |
||||
|
{ |
||||
|
// Distribute the items
|
||||
|
this._distributeItems(); |
||||
|
} |
||||
|
|
||||
|
// Items
|
||||
|
if ( 'items' in changes ) |
||||
|
{ |
||||
|
// Distribute the items
|
||||
|
this._distributeItems(); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* After view init |
||||
|
*/ |
||||
|
ngAfterViewInit(): void |
||||
|
{ |
||||
|
// Distribute the items for the first time
|
||||
|
this._distributeItems(); |
||||
|
} |
||||
|
|
||||
|
// -----------------------------------------------------------------------------------------------------
|
||||
|
// @ Private methods
|
||||
|
// -----------------------------------------------------------------------------------------------------
|
||||
|
|
||||
|
/** |
||||
|
* Distribute items into columns |
||||
|
*/ |
||||
|
private _distributeItems(): void |
||||
|
{ |
||||
|
// Return an empty array if there are no items
|
||||
|
if ( this.items.length === 0 ) |
||||
|
{ |
||||
|
this.distributedColumns = []; |
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
// Prepare the distributed columns array
|
||||
|
this.distributedColumns = Array.from(Array(this.columns), item => ({items: []})); |
||||
|
|
||||
|
// Distribute the items to columns
|
||||
|
for ( let i = 0; i < this.items.length; i++ ) |
||||
|
{ |
||||
|
this.distributedColumns[i % this.columns].items.push(this.items[i]); |
||||
|
} |
||||
|
} |
||||
|
} |
@ -0,0 +1,18 @@ |
|||||
|
import { NgModule } from '@angular/core'; |
||||
|
import { CommonModule } from '@angular/common'; |
||||
|
import { FuseMasonryComponent } from '@fuse/components/masonry/masonry.component'; |
||||
|
|
||||
|
@NgModule({ |
||||
|
declarations: [ |
||||
|
FuseMasonryComponent |
||||
|
], |
||||
|
imports : [ |
||||
|
CommonModule |
||||
|
], |
||||
|
exports : [ |
||||
|
FuseMasonryComponent |
||||
|
] |
||||
|
}) |
||||
|
export class FuseMasonryModule |
||||
|
{ |
||||
|
} |
@ -0,0 +1,2 @@ |
|||||
|
export * from '@fuse/components/masonry/masonry.component'; |
||||
|
export * from '@fuse/components/masonry/masonry.module'; |
@ -0,0 +1,126 @@ |
|||||
|
<!-- Item wrapper --> |
||||
|
<div |
||||
|
class="fuse-horizontal-navigation-item-wrapper" |
||||
|
[class.fuse-horizontal-navigation-item-has-subtitle]="!!item.subtitle" |
||||
|
[ngClass]="item.classes?.wrapper"> |
||||
|
|
||||
|
<!-- Item with an internal link --> |
||||
|
<ng-container *ngIf="item.link && !item.externalLink && !item.function && !item.disabled"> |
||||
|
<div |
||||
|
class="fuse-horizontal-navigation-item" |
||||
|
[ngClass]="{'fuse-horizontal-navigation-item-active-forced': item.active}" |
||||
|
[routerLink]="[item.link]" |
||||
|
[routerLinkActive]="'fuse-horizontal-navigation-item-active'" |
||||
|
[routerLinkActiveOptions]="isActiveMatchOptions" |
||||
|
[matTooltip]="item.tooltip || ''"> |
||||
|
<ng-container *ngTemplateOutlet="itemTemplate"></ng-container> |
||||
|
</div> |
||||
|
</ng-container> |
||||
|
|
||||
|
<!-- Item with an external link --> |
||||
|
<ng-container *ngIf="item.link && item.externalLink && !item.function && !item.disabled"> |
||||
|
<a |
||||
|
class="fuse-horizontal-navigation-item" |
||||
|
[href]="item.link" |
||||
|
[target]="item.target || '_self'" |
||||
|
[matTooltip]="item.tooltip || ''"> |
||||
|
<ng-container *ngTemplateOutlet="itemTemplate"></ng-container> |
||||
|
</a> |
||||
|
</ng-container> |
||||
|
|
||||
|
<!-- Item with a function --> |
||||
|
<ng-container *ngIf="!item.link && item.function && !item.disabled"> |
||||
|
<div |
||||
|
class="fuse-horizontal-navigation-item" |
||||
|
[ngClass]="{'fuse-horizontal-navigation-item-active-forced': item.active}" |
||||
|
[matTooltip]="item.tooltip || ''" |
||||
|
(click)="item.function(item)"> |
||||
|
<ng-container *ngTemplateOutlet="itemTemplate"></ng-container> |
||||
|
</div> |
||||
|
</ng-container> |
||||
|
|
||||
|
<!-- Item with an internal link and function --> |
||||
|
<ng-container *ngIf="item.link && !item.externalLink && item.function && !item.disabled"> |
||||
|
<div |
||||
|
class="fuse-horizontal-navigation-item" |
||||
|
[ngClass]="{'fuse-horizontal-navigation-item-active-forced': item.active}" |
||||
|
[routerLink]="[item.link]" |
||||
|
[routerLinkActive]="'fuse-horizontal-navigation-item-active'" |
||||
|
[routerLinkActiveOptions]="isActiveMatchOptions" |
||||
|
[matTooltip]="item.tooltip || ''" |
||||
|
(click)="item.function(item)"> |
||||
|
<ng-container *ngTemplateOutlet="itemTemplate"></ng-container> |
||||
|
</div> |
||||
|
</ng-container> |
||||
|
|
||||
|
<!-- Item with an external link and function --> |
||||
|
<ng-container *ngIf="item.link && item.externalLink && item.function && !item.disabled"> |
||||
|
<a |
||||
|
class="fuse-horizontal-navigation-item" |
||||
|
[href]="item.link" |
||||
|
[target]="item.target || '_self'" |
||||
|
[matTooltip]="item.tooltip || ''" |
||||
|
(click)="item.function(item)" |
||||
|
mat-menu-item> |
||||
|
<ng-container *ngTemplateOutlet="itemTemplate"></ng-container> |
||||
|
</a> |
||||
|
</ng-container> |
||||
|
|
||||
|
<!-- Item with a no link and no function --> |
||||
|
<ng-container *ngIf="!item.link && !item.function && !item.disabled"> |
||||
|
<div |
||||
|
class="fuse-horizontal-navigation-item" |
||||
|
[ngClass]="{'fuse-horizontal-navigation-item-active-forced': item.active}" |
||||
|
[matTooltip]="item.tooltip || ''"> |
||||
|
<ng-container *ngTemplateOutlet="itemTemplate"></ng-container> |
||||
|
</div> |
||||
|
</ng-container> |
||||
|
|
||||
|
<!-- Item is disabled --> |
||||
|
<ng-container *ngIf="item.disabled"> |
||||
|
<div class="fuse-horizontal-navigation-item fuse-horizontal-navigation-item-disabled"> |
||||
|
<ng-container *ngTemplateOutlet="itemTemplate"></ng-container> |
||||
|
</div> |
||||
|
</ng-container> |
||||
|
|
||||
|
</div> |
||||
|
|
||||
|
<!-- Item template --> |
||||
|
<ng-template #itemTemplate> |
||||
|
|
||||
|
<!-- Icon --> |
||||
|
<ng-container *ngIf="item.icon"> |
||||
|
<mat-icon |
||||
|
class="fuse-horizontal-navigation-item-icon" |
||||
|
[ngClass]="item.classes?.icon" |
||||
|
[svgIcon]="item.icon"></mat-icon> |
||||
|
</ng-container> |
||||
|
|
||||
|
<!-- Title & Subtitle --> |
||||
|
<div class="fuse-horizontal-navigation-item-title-wrapper"> |
||||
|
<div class="fuse-horizontal-navigation-item-title"> |
||||
|
<span [ngClass]="item.classes?.title"> |
||||
|
{{item.title}} |
||||
|
</span> |
||||
|
</div> |
||||
|
<ng-container *ngIf="item.subtitle"> |
||||
|
<div class="fuse-horizontal-navigation-item-subtitle text-hint"> |
||||
|
<span [ngClass]="item.classes?.subtitle"> |
||||
|
{{item.subtitle}} |
||||
|
</span> |
||||
|
</div> |
||||
|
</ng-container> |
||||
|
</div> |
||||
|
|
||||
|
<!-- Badge --> |
||||
|
<ng-container *ngIf="item.badge"> |
||||
|
<div class="fuse-horizontal-navigation-item-badge"> |
||||
|
<div |
||||
|
class="fuse-horizontal-navigation-item-badge-content" |
||||
|
[ngClass]="item.badge.classes"> |
||||
|
{{item.badge.title}} |
||||
|
</div> |
||||
|
</div> |
||||
|
</ng-container> |
||||
|
|
||||
|
</ng-template> |
@ -0,0 +1,82 @@ |
|||||
|
import { ChangeDetectionStrategy, ChangeDetectorRef, Component, Input, OnDestroy, OnInit } from '@angular/core'; |
||||
|
import { IsActiveMatchOptions } from '@angular/router'; |
||||
|
import { Subject } from 'rxjs'; |
||||
|
import { takeUntil } from 'rxjs/operators'; |
||||
|
import { FuseHorizontalNavigationComponent } from '@fuse/components/navigation/horizontal/horizontal.component'; |
||||
|
import { FuseNavigationService } from '@fuse/components/navigation/navigation.service'; |
||||
|
import { FuseNavigationItem } from '@fuse/components/navigation/navigation.types'; |
||||
|
import { FuseUtilsService } from '@fuse/services/utils/utils.service'; |
||||
|
|
||||
|
@Component({ |
||||
|
selector : 'fuse-horizontal-navigation-basic-item', |
||||
|
templateUrl : './basic.component.html', |
||||
|
changeDetection: ChangeDetectionStrategy.OnPush |
||||
|
}) |
||||
|
export class FuseHorizontalNavigationBasicItemComponent implements OnInit, OnDestroy |
||||
|
{ |
||||
|
@Input() item: FuseNavigationItem; |
||||
|
@Input() name: string; |
||||
|
|
||||
|
isActiveMatchOptions: IsActiveMatchOptions; |
||||
|
private _fuseHorizontalNavigationComponent: FuseHorizontalNavigationComponent; |
||||
|
private _unsubscribeAll: Subject<any> = new Subject<any>(); |
||||
|
|
||||
|
/** |
||||
|
* Constructor |
||||
|
*/ |
||||
|
constructor( |
||||
|
private _changeDetectorRef: ChangeDetectorRef, |
||||
|
private _fuseNavigationService: FuseNavigationService, |
||||
|
private _fuseUtilsService: FuseUtilsService |
||||
|
) |
||||
|
{ |
||||
|
// Set the equivalent of {exact: false} as default for active match options.
|
||||
|
// We are not assigning the item.isActiveMatchOptions directly to the
|
||||
|
// [routerLinkActiveOptions] because if it's "undefined" initially, the router
|
||||
|
// will throw an error and stop working.
|
||||
|
this.isActiveMatchOptions = this._fuseUtilsService.subsetMatchOptions; |
||||
|
} |
||||
|
|
||||
|
// -----------------------------------------------------------------------------------------------------
|
||||
|
// @ Lifecycle hooks
|
||||
|
// -----------------------------------------------------------------------------------------------------
|
||||
|
|
||||
|
/** |
||||
|
* On init |
||||
|
*/ |
||||
|
ngOnInit(): void |
||||
|
{ |
||||
|
// Set the "isActiveMatchOptions" either from item's
|
||||
|
// "isActiveMatchOptions" or the equivalent form of
|
||||
|
// item's "exactMatch" option
|
||||
|
this.isActiveMatchOptions = |
||||
|
this.item.isActiveMatchOptions ?? this.item.exactMatch |
||||
|
? this._fuseUtilsService.exactMatchOptions |
||||
|
: this._fuseUtilsService.subsetMatchOptions; |
||||
|
|
||||
|
// Get the parent navigation component
|
||||
|
this._fuseHorizontalNavigationComponent = this._fuseNavigationService.getComponent(this.name); |
||||
|
|
||||
|
// Mark for check
|
||||
|
this._changeDetectorRef.markForCheck(); |
||||
|
|
||||
|
// Subscribe to onRefreshed on the navigation component
|
||||
|
this._fuseHorizontalNavigationComponent.onRefreshed.pipe( |
||||
|
takeUntil(this._unsubscribeAll) |
||||
|
).subscribe(() => { |
||||
|
|
||||
|
// Mark for check
|
||||
|
this._changeDetectorRef.markForCheck(); |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* On destroy |
||||
|
*/ |
||||
|
ngOnDestroy(): void |
||||
|
{ |
||||
|
// Unsubscribe from all subscriptions
|
||||
|
this._unsubscribeAll.next(); |
||||
|
this._unsubscribeAll.complete(); |
||||
|
} |
||||
|
} |
@ -0,0 +1,121 @@ |
|||||
|
<ng-container *ngIf="!child"> |
||||
|
<div |
||||
|
[ngClass]="{'fuse-horizontal-navigation-menu-active': trigger.menuOpen, |
||||
|
'fuse-horizontal-navigation-menu-active-forced': item.active}" |
||||
|
[matMenuTriggerFor]="matMenu" |
||||
|
(onMenuOpen)="triggerChangeDetection()" |
||||
|
(onMenuClose)="triggerChangeDetection()" |
||||
|
#trigger="matMenuTrigger"> |
||||
|
<ng-container *ngTemplateOutlet="itemTemplate; context: {$implicit: item}"></ng-container> |
||||
|
</div> |
||||
|
</ng-container> |
||||
|
|
||||
|
<mat-menu |
||||
|
class="fuse-horizontal-navigation-menu-panel" |
||||
|
[overlapTrigger]="false" |
||||
|
#matMenu="matMenu"> |
||||
|
|
||||
|
<ng-container *ngFor="let item of item.children; trackBy: trackByFn"> |
||||
|
|
||||
|
<!-- Skip the hidden items --> |
||||
|
<ng-container *ngIf="(item.hidden && !item.hidden(item)) || !item.hidden"> |
||||
|
|
||||
|
<!-- Basic --> |
||||
|
<ng-container *ngIf="item.type === 'basic'"> |
||||
|
<div |
||||
|
class="fuse-horizontal-navigation-menu-item" |
||||
|
[disabled]="item.disabled" |
||||
|
mat-menu-item> |
||||
|
<fuse-horizontal-navigation-basic-item |
||||
|
[item]="item" |
||||
|
[name]="name"></fuse-horizontal-navigation-basic-item> |
||||
|
</div> |
||||
|
</ng-container> |
||||
|
|
||||
|
<!-- Branch: aside, collapsable, group --> |
||||
|
<ng-container *ngIf="item.type === 'aside' || item.type === 'collapsable' || item.type === 'group'"> |
||||
|
<div |
||||
|
class="fuse-horizontal-navigation-menu-item" |
||||
|
[disabled]="item.disabled" |
||||
|
[matMenuTriggerFor]="branch.matMenu" |
||||
|
mat-menu-item> |
||||
|
<ng-container *ngTemplateOutlet="itemTemplate; context: {$implicit: item}"></ng-container> |
||||
|
<fuse-horizontal-navigation-branch-item |
||||
|
[child]="true" |
||||
|
[item]="item" |
||||
|
[name]="name" |
||||
|
#branch></fuse-horizontal-navigation-branch-item> |
||||
|
</div> |
||||
|
</ng-container> |
||||
|
|
||||
|
<!-- Divider --> |
||||
|
<ng-container *ngIf="item.type === 'divider'"> |
||||
|
<div |
||||
|
class="fuse-horizontal-navigation-menu-item" |
||||
|
mat-menu-item> |
||||
|
<fuse-horizontal-navigation-divider-item |
||||
|
[item]="item" |
||||
|
[name]="name"></fuse-horizontal-navigation-divider-item> |
||||
|
</div> |
||||
|
</ng-container> |
||||
|
|
||||
|
</ng-container> |
||||
|
|
||||
|
</ng-container> |
||||
|
|
||||
|
</mat-menu> |
||||
|
|
||||
|
<!-- Item template --> |
||||
|
<ng-template |
||||
|
let-item |
||||
|
#itemTemplate> |
||||
|
|
||||
|
<div |
||||
|
class="fuse-horizontal-navigation-item-wrapper" |
||||
|
[class.fuse-horizontal-navigation-item-has-subtitle]="!!item.subtitle" |
||||
|
[ngClass]="item.classes?.wrapper"> |
||||
|
|
||||
|
<div |
||||
|
class="fuse-horizontal-navigation-item" |
||||
|
[ngClass]="{'fuse-horizontal-navigation-item-disabled': item.disabled, |
||||
|
'fuse-horizontal-navigation-item-active-forced': item.active}" |
||||
|
[matTooltip]="item.tooltip || ''"> |
||||
|
|
||||
|
<!-- Icon --> |
||||
|
<ng-container *ngIf="item.icon"> |
||||
|
<mat-icon |
||||
|
class="fuse-horizontal-navigation-item-icon" |
||||
|
[ngClass]="item.classes?.icon" |
||||
|
[svgIcon]="item.icon"></mat-icon> |
||||
|
</ng-container> |
||||
|
|
||||
|
<!-- Title & Subtitle --> |
||||
|
<div class="fuse-horizontal-navigation-item-title-wrapper"> |
||||
|
<div class="fuse-horizontal-navigation-item-title"> |
||||
|
<span [ngClass]="item.classes?.title"> |
||||
|
{{item.title}} |
||||
|
</span> |
||||
|
</div> |
||||
|
<ng-container *ngIf="item.subtitle"> |
||||
|
<div class="fuse-horizontal-navigation-item-subtitle text-hint"> |
||||
|
<span [ngClass]="item.classes?.subtitle"> |
||||
|
{{item.subtitle}} |
||||
|
</span> |
||||
|
</div> |
||||
|
</ng-container> |
||||
|
</div> |
||||
|
|
||||
|
<!-- Badge --> |
||||
|
<ng-container *ngIf="item.badge"> |
||||
|
<div class="fuse-horizontal-navigation-item-badge"> |
||||
|
<div |
||||
|
class="fuse-horizontal-navigation-item-badge-content" |
||||
|
[ngClass]="item.badge.classes"> |
||||
|
{{item.badge.title}} |
||||
|
</div> |
||||
|
</div> |
||||
|
</ng-container> |
||||
|
</div> |
||||
|
</div> |
||||
|
|
||||
|
</ng-template> |
@ -0,0 +1,94 @@ |
|||||
|
import { ChangeDetectionStrategy, ChangeDetectorRef, Component, Input, OnDestroy, OnInit, ViewChild } from '@angular/core'; |
||||
|
import { BooleanInput } from '@angular/cdk/coercion'; |
||||
|
import { MatMenu } from '@angular/material/menu'; |
||||
|
import { Subject } from 'rxjs'; |
||||
|
import { takeUntil } from 'rxjs/operators'; |
||||
|
import { FuseHorizontalNavigationComponent } from '@fuse/components/navigation/horizontal/horizontal.component'; |
||||
|
import { FuseNavigationService } from '@fuse/components/navigation/navigation.service'; |
||||
|
import { FuseNavigationItem } from '@fuse/components/navigation/navigation.types'; |
||||
|
|
||||
|
@Component({ |
||||
|
selector : 'fuse-horizontal-navigation-branch-item', |
||||
|
templateUrl : './branch.component.html', |
||||
|
changeDetection: ChangeDetectionStrategy.OnPush |
||||
|
}) |
||||
|
export class FuseHorizontalNavigationBranchItemComponent implements OnInit, OnDestroy |
||||
|
{ |
||||
|
/* eslint-disable @typescript-eslint/naming-convention */ |
||||
|
static ngAcceptInputType_child: BooleanInput; |
||||
|
/* eslint-enable @typescript-eslint/naming-convention */ |
||||
|
|
||||
|
@Input() child: boolean = false; |
||||
|
@Input() item: FuseNavigationItem; |
||||
|
@Input() name: string; |
||||
|
@ViewChild('matMenu', {static: true}) matMenu: MatMenu; |
||||
|
|
||||
|
private _fuseHorizontalNavigationComponent: FuseHorizontalNavigationComponent; |
||||
|
private _unsubscribeAll: Subject<any> = new Subject<any>(); |
||||
|
|
||||
|
/** |
||||
|
* Constructor |
||||
|
*/ |
||||
|
constructor( |
||||
|
private _changeDetectorRef: ChangeDetectorRef, |
||||
|
private _fuseNavigationService: FuseNavigationService |
||||
|
) |
||||
|
{ |
||||
|
} |
||||
|
|
||||
|
// -----------------------------------------------------------------------------------------------------
|
||||
|
// @ Lifecycle hooks
|
||||
|
// -----------------------------------------------------------------------------------------------------
|
||||
|
|
||||
|
/** |
||||
|
* On init |
||||
|
*/ |
||||
|
ngOnInit(): void |
||||
|
{ |
||||
|
// Get the parent navigation component
|
||||
|
this._fuseHorizontalNavigationComponent = this._fuseNavigationService.getComponent(this.name); |
||||
|
|
||||
|
// Subscribe to onRefreshed on the navigation component
|
||||
|
this._fuseHorizontalNavigationComponent.onRefreshed.pipe( |
||||
|
takeUntil(this._unsubscribeAll) |
||||
|
).subscribe(() => { |
||||
|
|
||||
|
// Mark for check
|
||||
|
this._changeDetectorRef.markForCheck(); |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* On destroy |
||||
|
*/ |
||||
|
ngOnDestroy(): void |
||||
|
{ |
||||
|
// Unsubscribe from all subscriptions
|
||||
|
this._unsubscribeAll.next(); |
||||
|
this._unsubscribeAll.complete(); |
||||
|
} |
||||
|
|
||||
|
// -----------------------------------------------------------------------------------------------------
|
||||
|
// @ Public methods
|
||||
|
// -----------------------------------------------------------------------------------------------------
|
||||
|
|
||||
|
/** |
||||
|
* Trigger the change detection |
||||
|
*/ |
||||
|
triggerChangeDetection(): void |
||||
|
{ |
||||
|
// Mark for check
|
||||
|
this._changeDetectorRef.markForCheck(); |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* Track by function for ngFor loops |
||||
|
* |
||||
|
* @param index |
||||
|
* @param item |
||||
|
*/ |
||||
|
trackByFn(index: number, item: any): any |
||||
|
{ |
||||
|
return item.id || index; |
||||
|
} |
||||
|
} |
@ -0,0 +1,4 @@ |
|||||
|
<!-- Divider --> |
||||
|
<div |
||||
|
class="fuse-horizontal-navigation-item-wrapper divider" |
||||
|
[ngClass]="item.classes?.wrapper"></div> |
@ -0,0 +1,62 @@ |
|||||
|
import { ChangeDetectionStrategy, ChangeDetectorRef, Component, Input, OnDestroy, OnInit } from '@angular/core'; |
||||
|
import { Subject } from 'rxjs'; |
||||
|
import { takeUntil } from 'rxjs/operators'; |
||||
|
import { FuseHorizontalNavigationComponent } from '@fuse/components/navigation/horizontal/horizontal.component'; |
||||
|
import { FuseNavigationService } from '@fuse/components/navigation/navigation.service'; |
||||
|
import { FuseNavigationItem } from '@fuse/components/navigation/navigation.types'; |
||||
|
|
||||
|
@Component({ |
||||
|
selector : 'fuse-horizontal-navigation-divider-item', |
||||
|
templateUrl : './divider.component.html', |
||||
|
changeDetection: ChangeDetectionStrategy.OnPush |
||||
|
}) |
||||
|
export class FuseHorizontalNavigationDividerItemComponent implements OnInit, OnDestroy |
||||
|
{ |
||||
|
@Input() item: FuseNavigationItem; |
||||
|
@Input() name: string; |
||||
|
|
||||
|
private _fuseHorizontalNavigationComponent: FuseHorizontalNavigationComponent; |
||||
|
private _unsubscribeAll: Subject<any> = new Subject<any>(); |
||||
|
|
||||
|
/** |
||||
|
* Constructor |
||||
|
*/ |
||||
|
constructor( |
||||
|
private _changeDetectorRef: ChangeDetectorRef, |
||||
|
private _fuseNavigationService: FuseNavigationService |
||||
|
) |
||||
|
{ |
||||
|
} |
||||
|
|
||||
|
// -----------------------------------------------------------------------------------------------------
|
||||
|
// @ Lifecycle hooks
|
||||
|
// -----------------------------------------------------------------------------------------------------
|
||||
|
|
||||
|
/** |
||||
|
* On init |
||||
|
*/ |
||||
|
ngOnInit(): void |
||||
|
{ |
||||
|
// Get the parent navigation component
|
||||
|
this._fuseHorizontalNavigationComponent = this._fuseNavigationService.getComponent(this.name); |
||||
|
|
||||
|
// Subscribe to onRefreshed on the navigation component
|
||||
|
this._fuseHorizontalNavigationComponent.onRefreshed.pipe( |
||||
|
takeUntil(this._unsubscribeAll) |
||||
|
).subscribe(() => { |
||||
|
|
||||
|
// Mark for check
|
||||
|
this._changeDetectorRef.markForCheck(); |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* On destroy |
||||
|
*/ |
||||
|
ngOnDestroy(): void |
||||
|
{ |
||||
|
// Unsubscribe from all subscriptions
|
||||
|
this._unsubscribeAll.next(); |
||||
|
this._unsubscribeAll.complete(); |
||||
|
} |
||||
|
} |
@ -0,0 +1,4 @@ |
|||||
|
<!-- Spacer --> |
||||
|
<div |
||||
|
class="fuse-horizontal-navigation-item-wrapper" |
||||
|
[ngClass]="item.classes?.wrapper"></div> |
@ -0,0 +1,62 @@ |
|||||
|
import { ChangeDetectionStrategy, ChangeDetectorRef, Component, Input, OnDestroy, OnInit } from '@angular/core'; |
||||
|
import { takeUntil } from 'rxjs/operators'; |
||||
|
import { Subject } from 'rxjs'; |
||||
|
import { FuseHorizontalNavigationComponent } from '@fuse/components/navigation/horizontal/horizontal.component'; |
||||
|
import { FuseNavigationService } from '@fuse/components/navigation/navigation.service'; |
||||
|
import { FuseNavigationItem } from '@fuse/components/navigation/navigation.types'; |
||||
|
|
||||
|
@Component({ |
||||
|
selector : 'fuse-horizontal-navigation-spacer-item', |
||||
|
templateUrl : './spacer.component.html', |
||||
|
changeDetection: ChangeDetectionStrategy.OnPush |
||||
|
}) |
||||
|
export class FuseHorizontalNavigationSpacerItemComponent implements OnInit, OnDestroy |
||||
|
{ |
||||
|
@Input() item: FuseNavigationItem; |
||||
|
@Input() name: string; |
||||
|
|
||||
|
private _fuseHorizontalNavigationComponent: FuseHorizontalNavigationComponent; |
||||
|
private _unsubscribeAll: Subject<any> = new Subject<any>(); |
||||
|
|
||||
|
/** |
||||
|
* Constructor |
||||
|
*/ |
||||
|
constructor( |
||||
|
private _changeDetectorRef: ChangeDetectorRef, |
||||
|
private _fuseNavigationService: FuseNavigationService |
||||
|
) |
||||
|
{ |
||||
|
} |
||||
|
|
||||
|
// -----------------------------------------------------------------------------------------------------
|
||||
|
// @ Lifecycle hooks
|
||||
|
// -----------------------------------------------------------------------------------------------------
|
||||
|
|
||||
|
/** |
||||
|
* On init |
||||
|
*/ |
||||
|
ngOnInit(): void |
||||
|
{ |
||||
|
// Get the parent navigation component
|
||||
|
this._fuseHorizontalNavigationComponent = this._fuseNavigationService.getComponent(this.name); |
||||
|
|
||||
|
// Subscribe to onRefreshed on the navigation component
|
||||
|
this._fuseHorizontalNavigationComponent.onRefreshed.pipe( |
||||
|
takeUntil(this._unsubscribeAll) |
||||
|
).subscribe(() => { |
||||
|
|
||||
|
// Mark for check
|
||||
|
this._changeDetectorRef.markForCheck(); |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* On destroy |
||||
|
*/ |
||||
|
ngOnDestroy(): void |
||||
|
{ |
||||
|
// Unsubscribe from all subscriptions
|
||||
|
this._unsubscribeAll.next(); |
||||
|
this._unsubscribeAll.complete(); |
||||
|
} |
||||
|
} |
@ -0,0 +1,36 @@ |
|||||
|
<div class="fuse-horizontal-navigation-wrapper"> |
||||
|
|
||||
|
<ng-container *ngFor="let item of navigation; trackBy: trackByFn"> |
||||
|
|
||||
|
<!-- Skip the hidden items --> |
||||
|
<ng-container *ngIf="(item.hidden && !item.hidden(item)) || !item.hidden"> |
||||
|
|
||||
|
<!-- Basic --> |
||||
|
<ng-container *ngIf="item.type === 'basic'"> |
||||
|
<fuse-horizontal-navigation-basic-item |
||||
|
class="fuse-horizontal-navigation-menu-item" |
||||
|
[item]="item" |
||||
|
[name]="name"></fuse-horizontal-navigation-basic-item> |
||||
|
</ng-container> |
||||
|
|
||||
|
<!-- Branch: aside, collapsable, group --> |
||||
|
<ng-container *ngIf="item.type === 'aside' || item.type === 'collapsable' || item.type === 'group'"> |
||||
|
<fuse-horizontal-navigation-branch-item |
||||
|
class="fuse-horizontal-navigation-menu-item" |
||||
|
[item]="item" |
||||
|
[name]="name"></fuse-horizontal-navigation-branch-item> |
||||
|
</ng-container> |
||||
|
|
||||
|
<!-- Spacer --> |
||||
|
<ng-container *ngIf="item.type === 'spacer'"> |
||||
|
<fuse-horizontal-navigation-spacer-item |
||||
|
class="fuse-horizontal-navigation-menu-item" |
||||
|
[item]="item" |
||||
|
[name]="name"></fuse-horizontal-navigation-spacer-item> |
||||
|
</ng-container> |
||||
|
|
||||
|
</ng-container> |
||||
|
|
||||
|
</ng-container> |
||||
|
|
||||
|
</div> |
@ -0,0 +1,180 @@ |
|||||
|
/* Root navigation specific */ |
||||
|
fuse-horizontal-navigation { |
||||
|
|
||||
|
.fuse-horizontal-navigation-wrapper { |
||||
|
display: flex; |
||||
|
align-items: center; |
||||
|
|
||||
|
/* Basic, Branch */ |
||||
|
fuse-horizontal-navigation-basic-item, |
||||
|
fuse-horizontal-navigation-branch-item { |
||||
|
|
||||
|
@screen sm { |
||||
|
|
||||
|
&:hover { |
||||
|
|
||||
|
.fuse-horizontal-navigation-item-wrapper { |
||||
|
@apply bg-hover; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
.fuse-horizontal-navigation-item-wrapper { |
||||
|
border-radius: 4px; |
||||
|
overflow: hidden; |
||||
|
|
||||
|
.fuse-horizontal-navigation-item { |
||||
|
padding: 0 16px; |
||||
|
cursor: pointer; |
||||
|
user-select: none; |
||||
|
|
||||
|
.fuse-horizontal-navigation-item-icon { |
||||
|
margin-right: 12px; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/* Basic - When item active (current link) */ |
||||
|
fuse-horizontal-navigation-basic-item { |
||||
|
|
||||
|
.fuse-horizontal-navigation-item-active, |
||||
|
.fuse-horizontal-navigation-item-active-forced { |
||||
|
|
||||
|
.fuse-horizontal-navigation-item-title { |
||||
|
@apply text-primary #{'!important'}; |
||||
|
} |
||||
|
|
||||
|
.fuse-horizontal-navigation-item-subtitle { |
||||
|
@apply text-primary-400 #{'!important'}; |
||||
|
|
||||
|
.dark & { |
||||
|
@apply text-primary-600 #{'!important'}; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
.fuse-horizontal-navigation-item-icon { |
||||
|
@apply text-primary #{'!important'}; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/* Branch - When menu open */ |
||||
|
fuse-horizontal-navigation-branch-item { |
||||
|
|
||||
|
.fuse-horizontal-navigation-menu-active, |
||||
|
.fuse-horizontal-navigation-menu-active-forced { |
||||
|
|
||||
|
.fuse-horizontal-navigation-item-wrapper { |
||||
|
@apply bg-hover; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/* Spacer */ |
||||
|
fuse-horizontal-navigation-spacer-item { |
||||
|
margin: 12px 0; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/* Menu panel specific */ |
||||
|
.fuse-horizontal-navigation-menu-panel { |
||||
|
|
||||
|
.fuse-horizontal-navigation-menu-item { |
||||
|
height: auto; |
||||
|
min-height: 0; |
||||
|
line-height: normal; |
||||
|
white-space: normal; |
||||
|
|
||||
|
/* Basic, Branch */ |
||||
|
fuse-horizontal-navigation-basic-item, |
||||
|
fuse-horizontal-navigation-branch-item, |
||||
|
fuse-horizontal-navigation-divider-item { |
||||
|
display: flex; |
||||
|
flex: 1 1 auto; |
||||
|
} |
||||
|
|
||||
|
/* Divider */ |
||||
|
fuse-horizontal-navigation-divider-item { |
||||
|
margin: 8px -16px; |
||||
|
|
||||
|
.fuse-horizontal-navigation-item-wrapper { |
||||
|
height: 1px; |
||||
|
box-shadow: 0 1px 0 0; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/* Navigation menu item common */ |
||||
|
.fuse-horizontal-navigation-menu-item { |
||||
|
|
||||
|
/* Basic - When item active (current link) */ |
||||
|
fuse-horizontal-navigation-basic-item { |
||||
|
|
||||
|
.fuse-horizontal-navigation-item-active, |
||||
|
.fuse-horizontal-navigation-item-active-forced { |
||||
|
|
||||
|
.fuse-horizontal-navigation-item-title { |
||||
|
@apply text-primary #{'!important'}; |
||||
|
} |
||||
|
|
||||
|
.fuse-horizontal-navigation-item-subtitle { |
||||
|
@apply text-primary-400 #{'!important'}; |
||||
|
|
||||
|
.dark & { |
||||
|
@apply text-primary-600 #{'!important'}; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
.fuse-horizontal-navigation-item-icon { |
||||
|
@apply text-primary #{'!important'}; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
.fuse-horizontal-navigation-item-wrapper { |
||||
|
width: 100%; |
||||
|
|
||||
|
&.fuse-horizontal-navigation-item-has-subtitle { |
||||
|
|
||||
|
.fuse-horizontal-navigation-item { |
||||
|
min-height: 56px; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
.fuse-horizontal-navigation-item { |
||||
|
position: relative; |
||||
|
display: flex; |
||||
|
align-items: center; |
||||
|
justify-content: flex-start; |
||||
|
min-height: 48px; |
||||
|
width: 100%; |
||||
|
font-size: 13px; |
||||
|
font-weight: 500; |
||||
|
text-decoration: none; |
||||
|
|
||||
|
.fuse-horizontal-navigation-item-title-wrapper { |
||||
|
|
||||
|
.fuse-horizontal-navigation-item-subtitle { |
||||
|
font-size: 12px; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
.fuse-horizontal-navigation-item-badge { |
||||
|
margin-left: auto; |
||||
|
|
||||
|
.fuse-horizontal-navigation-item-badge-content { |
||||
|
display: flex; |
||||
|
align-items: center; |
||||
|
justify-content: center; |
||||
|
font-size: 10px; |
||||
|
font-weight: 600; |
||||
|
white-space: nowrap; |
||||
|
height: 20px; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
@ -0,0 +1,109 @@ |
|||||
|
import { ChangeDetectionStrategy, ChangeDetectorRef, Component, Input, OnChanges, OnDestroy, OnInit, SimpleChanges, ViewEncapsulation } from '@angular/core'; |
||||
|
import { ReplaySubject, Subject } from 'rxjs'; |
||||
|
import { fuseAnimations } from '@fuse/animations'; |
||||
|
import { FuseNavigationItem } from '@fuse/components/navigation/navigation.types'; |
||||
|
import { FuseNavigationService } from '@fuse/components/navigation/navigation.service'; |
||||
|
import { FuseUtilsService } from '@fuse/services/utils/utils.service'; |
||||
|
|
||||
|
@Component({ |
||||
|
selector : 'fuse-horizontal-navigation', |
||||
|
templateUrl : './horizontal.component.html', |
||||
|
styleUrls : ['./horizontal.component.scss'], |
||||
|
animations : fuseAnimations, |
||||
|
encapsulation : ViewEncapsulation.None, |
||||
|
changeDetection: ChangeDetectionStrategy.OnPush, |
||||
|
exportAs : 'fuseHorizontalNavigation' |
||||
|
}) |
||||
|
export class FuseHorizontalNavigationComponent implements OnChanges, OnInit, OnDestroy |
||||
|
{ |
||||
|
@Input() name: string = this._fuseUtilsService.randomId(); |
||||
|
@Input() navigation: FuseNavigationItem[]; |
||||
|
|
||||
|
onRefreshed: ReplaySubject<boolean> = new ReplaySubject<boolean>(1); |
||||
|
private _unsubscribeAll: Subject<any> = new Subject<any>(); |
||||
|
|
||||
|
/** |
||||
|
* Constructor |
||||
|
*/ |
||||
|
constructor( |
||||
|
private _changeDetectorRef: ChangeDetectorRef, |
||||
|
private _fuseNavigationService: FuseNavigationService, |
||||
|
private _fuseUtilsService: FuseUtilsService |
||||
|
) |
||||
|
{ |
||||
|
} |
||||
|
|
||||
|
// -----------------------------------------------------------------------------------------------------
|
||||
|
// @ Lifecycle hooks
|
||||
|
// -----------------------------------------------------------------------------------------------------
|
||||
|
|
||||
|
/** |
||||
|
* On changes |
||||
|
* |
||||
|
* @param changes |
||||
|
*/ |
||||
|
ngOnChanges(changes: SimpleChanges): void |
||||
|
{ |
||||
|
// Navigation
|
||||
|
if ( 'navigation' in changes ) |
||||
|
{ |
||||
|
// Mark for check
|
||||
|
this._changeDetectorRef.markForCheck(); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* On init |
||||
|
*/ |
||||
|
ngOnInit(): void |
||||
|
{ |
||||
|
// Make sure the name input is not an empty string
|
||||
|
if ( this.name === '' ) |
||||
|
{ |
||||
|
this.name = this._fuseUtilsService.randomId(); |
||||
|
} |
||||
|
|
||||
|
// Register the navigation component
|
||||
|
this._fuseNavigationService.registerComponent(this.name, this); |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* On destroy |
||||
|
*/ |
||||
|
ngOnDestroy(): void |
||||
|
{ |
||||
|
// Deregister the navigation component from the registry
|
||||
|
this._fuseNavigationService.deregisterComponent(this.name); |
||||
|
|
||||
|
// Unsubscribe from all subscriptions
|
||||
|
this._unsubscribeAll.next(); |
||||
|
this._unsubscribeAll.complete(); |
||||
|
} |
||||
|
|
||||
|
// -----------------------------------------------------------------------------------------------------
|
||||
|
// @ Public methods
|
||||
|
// -----------------------------------------------------------------------------------------------------
|
||||
|
|
||||
|
/** |
||||
|
* Refresh the component to apply the changes |
||||
|
*/ |
||||
|
refresh(): void |
||||
|
{ |
||||
|
// Mark for check
|
||||
|
this._changeDetectorRef.markForCheck(); |
||||
|
|
||||
|
// Execute the observable
|
||||
|
this.onRefreshed.next(true); |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* Track by function for ngFor loops |
||||
|
* |
||||
|
* @param index |
||||
|
* @param item |
||||
|
*/ |
||||
|
trackByFn(index: number, item: any): any |
||||
|
{ |
||||
|
return item.id || index; |
||||
|
} |
||||
|
} |
@ -0,0 +1 @@ |
|||||
|
export * from '@fuse/components/navigation/public-api'; |
@ -0,0 +1,55 @@ |
|||||
|
import { NgModule } from '@angular/core'; |
||||
|
import { CommonModule } from '@angular/common'; |
||||
|
import { RouterModule } from '@angular/router'; |
||||
|
import { MatButtonModule } from '@angular/material/button'; |
||||
|
import { MatDividerModule } from '@angular/material/divider'; |
||||
|
import { MatIconModule } from '@angular/material/icon'; |
||||
|
import { MatMenuModule } from '@angular/material/menu'; |
||||
|
import { MatTooltipModule } from '@angular/material/tooltip'; |
||||
|
import { FuseScrollbarModule } from '@fuse/directives/scrollbar/public-api'; |
||||
|
import { FuseHorizontalNavigationBasicItemComponent } from '@fuse/components/navigation/horizontal/components/basic/basic.component'; |
||||
|
import { FuseHorizontalNavigationBranchItemComponent } from '@fuse/components/navigation/horizontal/components/branch/branch.component'; |
||||
|
import { FuseHorizontalNavigationDividerItemComponent } from '@fuse/components/navigation/horizontal/components/divider/divider.component'; |
||||
|
import { FuseHorizontalNavigationSpacerItemComponent } from '@fuse/components/navigation/horizontal/components/spacer/spacer.component'; |
||||
|
import { FuseHorizontalNavigationComponent } from '@fuse/components/navigation/horizontal/horizontal.component'; |
||||
|
import { FuseVerticalNavigationAsideItemComponent } from '@fuse/components/navigation/vertical/components/aside/aside.component'; |
||||
|
import { FuseVerticalNavigationBasicItemComponent } from '@fuse/components/navigation/vertical/components/basic/basic.component'; |
||||
|
import { FuseVerticalNavigationCollapsableItemComponent } from '@fuse/components/navigation/vertical/components/collapsable/collapsable.component'; |
||||
|
import { FuseVerticalNavigationDividerItemComponent } from '@fuse/components/navigation/vertical/components/divider/divider.component'; |
||||
|
import { FuseVerticalNavigationGroupItemComponent } from '@fuse/components/navigation/vertical/components/group/group.component'; |
||||
|
import { FuseVerticalNavigationSpacerItemComponent } from '@fuse/components/navigation/vertical/components/spacer/spacer.component'; |
||||
|
import { FuseVerticalNavigationComponent } from '@fuse/components/navigation/vertical/vertical.component'; |
||||
|
|
||||
|
@NgModule({ |
||||
|
declarations: [ |
||||
|
FuseHorizontalNavigationBasicItemComponent, |
||||
|
FuseHorizontalNavigationBranchItemComponent, |
||||
|
FuseHorizontalNavigationDividerItemComponent, |
||||
|
FuseHorizontalNavigationSpacerItemComponent, |
||||
|
FuseHorizontalNavigationComponent, |
||||
|
FuseVerticalNavigationAsideItemComponent, |
||||
|
FuseVerticalNavigationBasicItemComponent, |
||||
|
FuseVerticalNavigationCollapsableItemComponent, |
||||
|
FuseVerticalNavigationDividerItemComponent, |
||||
|
FuseVerticalNavigationGroupItemComponent, |
||||
|
FuseVerticalNavigationSpacerItemComponent, |
||||
|
FuseVerticalNavigationComponent |
||||
|
], |
||||
|
imports : [ |
||||
|
CommonModule, |
||||
|
RouterModule, |
||||
|
MatButtonModule, |
||||
|
MatDividerModule, |
||||
|
MatIconModule, |
||||
|
MatMenuModule, |
||||
|
MatTooltipModule, |
||||
|
FuseScrollbarModule |
||||
|
], |
||||
|
exports : [ |
||||
|
FuseHorizontalNavigationComponent, |
||||
|
FuseVerticalNavigationComponent |
||||
|
] |
||||
|
}) |
||||
|
export class FuseNavigationModule |
||||
|
{ |
||||
|
} |
@ -0,0 +1,186 @@ |
|||||
|
import { Injectable } from '@angular/core'; |
||||
|
import { FuseNavigationItem } from '@fuse/components/navigation/navigation.types'; |
||||
|
|
||||
|
@Injectable({ |
||||
|
providedIn: 'root' |
||||
|
}) |
||||
|
export class FuseNavigationService |
||||
|
{ |
||||
|
private _componentRegistry: Map<string, any> = new Map<string, any>(); |
||||
|
private _navigationStore: Map<string, FuseNavigationItem[]> = new Map<string, any>(); |
||||
|
|
||||
|
/** |
||||
|
* Constructor |
||||
|
*/ |
||||
|
constructor() |
||||
|
{ |
||||
|
} |
||||
|
|
||||
|
// -----------------------------------------------------------------------------------------------------
|
||||
|
// @ Public methods
|
||||
|
// -----------------------------------------------------------------------------------------------------
|
||||
|
|
||||
|
/** |
||||
|
* Register navigation component |
||||
|
* |
||||
|
* @param name |
||||
|
* @param component |
||||
|
*/ |
||||
|
registerComponent(name: string, component: any): void |
||||
|
{ |
||||
|
this._componentRegistry.set(name, component); |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* Deregister navigation component |
||||
|
* |
||||
|
* @param name |
||||
|
*/ |
||||
|
deregisterComponent(name: string): void |
||||
|
{ |
||||
|
this._componentRegistry.delete(name); |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* Get navigation component from the registry |
||||
|
* |
||||
|
* @param name |
||||
|
*/ |
||||
|
getComponent<T>(name: string): T |
||||
|
{ |
||||
|
return this._componentRegistry.get(name); |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* Store the given navigation with the given key |
||||
|
* |
||||
|
* @param key |
||||
|
* @param navigation |
||||
|
*/ |
||||
|
storeNavigation(key: string, navigation: FuseNavigationItem[]): void |
||||
|
{ |
||||
|
// Add to the store
|
||||
|
this._navigationStore.set(key, navigation); |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* Get navigation from storage by key |
||||
|
* |
||||
|
* @param key |
||||
|
*/ |
||||
|
getNavigation(key: string): FuseNavigationItem[] |
||||
|
{ |
||||
|
return this._navigationStore.get(key) ?? []; |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* Delete the navigation from the storage |
||||
|
* |
||||
|
* @param key |
||||
|
*/ |
||||
|
deleteNavigation(key: string): void |
||||
|
{ |
||||
|
// Check if the navigation exists
|
||||
|
if ( !this._navigationStore.has(key) ) |
||||
|
{ |
||||
|
console.warn(`Navigation with the key '${key}' does not exist in the store.`); |
||||
|
} |
||||
|
|
||||
|
// Delete from the storage
|
||||
|
this._navigationStore.delete(key); |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* Utility function that returns a flattened |
||||
|
* version of the given navigation array |
||||
|
* |
||||
|
* @param navigation |
||||
|
* @param flatNavigation |
||||
|
*/ |
||||
|
getFlatNavigation(navigation: FuseNavigationItem[], flatNavigation: FuseNavigationItem[] = []): FuseNavigationItem[] |
||||
|
{ |
||||
|
for ( const item of navigation ) |
||||
|
{ |
||||
|
if ( item.type === 'basic' ) |
||||
|
{ |
||||
|
flatNavigation.push(item); |
||||
|
continue; |
||||
|
} |
||||
|
|
||||
|
if ( item.type === 'aside' || item.type === 'collapsable' || item.type === 'group' ) |
||||
|
{ |
||||
|
if ( item.children ) |
||||
|
{ |
||||
|
this.getFlatNavigation(item.children, flatNavigation); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
return flatNavigation; |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* Utility function that returns the item |
||||
|
* with the given id from given navigation |
||||
|
* |
||||
|
* @param id |
||||
|
* @param navigation |
||||
|
*/ |
||||
|
getItem(id: string, navigation: FuseNavigationItem[]): FuseNavigationItem | null |
||||
|
{ |
||||
|
for ( const item of navigation ) |
||||
|
{ |
||||
|
if ( item.id === id ) |
||||
|
{ |
||||
|
return item; |
||||
|
} |
||||
|
|
||||
|
if ( item.children ) |
||||
|
{ |
||||
|
const childItem = this.getItem(id, item.children); |
||||
|
|
||||
|
if ( childItem ) |
||||
|
{ |
||||
|
return childItem; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
return null; |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* Utility function that returns the item's parent |
||||
|
* with the given id from given navigation |
||||
|
* |
||||
|
* @param id |
||||
|
* @param navigation |
||||
|
* @param parent |
||||
|
*/ |
||||
|
getItemParent( |
||||
|
id: string, |
||||
|
navigation: FuseNavigationItem[], |
||||
|
parent: FuseNavigationItem[] | FuseNavigationItem |
||||
|
): FuseNavigationItem[] | FuseNavigationItem | null |
||||
|
{ |
||||
|
for ( const item of navigation ) |
||||
|
{ |
||||
|
if ( item.id === id ) |
||||
|
{ |
||||
|
return parent; |
||||
|
} |
||||
|
|
||||
|
if ( item.children ) |
||||
|
{ |
||||
|
const childItem = this.getItemParent(id, item.children, item); |
||||
|
|
||||
|
if ( childItem ) |
||||
|
{ |
||||
|
return childItem; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
return null; |
||||
|
} |
||||
|
} |
@ -0,0 +1,57 @@ |
|||||
|
import { IsActiveMatchOptions } from '@angular/router'; |
||||
|
|
||||
|
export interface FuseNavigationItem |
||||
|
{ |
||||
|
id?: string; |
||||
|
title?: string; |
||||
|
subtitle?: string; |
||||
|
type: |
||||
|
| 'aside' |
||||
|
| 'basic' |
||||
|
| 'collapsable' |
||||
|
| 'divider' |
||||
|
| 'group' |
||||
|
| 'spacer'; |
||||
|
hidden?: (item: FuseNavigationItem) => boolean; |
||||
|
active?: boolean; |
||||
|
disabled?: boolean; |
||||
|
tooltip?: string; |
||||
|
link?: string; |
||||
|
externalLink?: boolean; |
||||
|
target?: |
||||
|
| '_blank' |
||||
|
| '_self' |
||||
|
| '_parent' |
||||
|
| '_top' |
||||
|
| string; |
||||
|
exactMatch?: boolean; |
||||
|
isActiveMatchOptions?: IsActiveMatchOptions; |
||||
|
function?: (item: FuseNavigationItem) => void; |
||||
|
classes?: { |
||||
|
title?: string; |
||||
|
subtitle?: string; |
||||
|
icon?: string; |
||||
|
wrapper?: string; |
||||
|
}; |
||||
|
icon?: string; |
||||
|
badge?: { |
||||
|
title?: string; |
||||
|
classes?: string; |
||||
|
}; |
||||
|
children?: FuseNavigationItem[]; |
||||
|
meta?: any; |
||||
|
} |
||||
|
|
||||
|
export type FuseVerticalNavigationAppearance = |
||||
|
| 'default' |
||||
|
| 'compact' |
||||
|
| 'dense' |
||||
|
| 'thin'; |
||||
|
|
||||
|
export type FuseVerticalNavigationMode = |
||||
|
| 'over' |
||||
|
| 'side'; |
||||
|
|
||||
|
export type FuseVerticalNavigationPosition = |
||||
|
| 'left' |
||||
|
| 'right'; |
@ -0,0 +1,5 @@ |
|||||
|
export * from '@fuse/components/navigation/horizontal/horizontal.component'; |
||||
|
export * from '@fuse/components/navigation/vertical/vertical.component'; |
||||
|
export * from '@fuse/components/navigation/navigation.module'; |
||||
|
export * from '@fuse/components/navigation/navigation.service'; |
||||
|
export * from '@fuse/components/navigation/navigation.types'; |
@ -0,0 +1,103 @@ |
|||||
|
<div |
||||
|
class="fuse-vertical-navigation-item-wrapper" |
||||
|
[class.fuse-vertical-navigation-item-has-subtitle]="!!item.subtitle" |
||||
|
[ngClass]="item.classes?.wrapper"> |
||||
|
|
||||
|
<div |
||||
|
class="fuse-vertical-navigation-item" |
||||
|
[ngClass]="{'fuse-vertical-navigation-item-active': active, |
||||
|
'fuse-vertical-navigation-item-disabled': item.disabled, |
||||
|
'fuse-vertical-navigation-item-active-forced': item.active}" |
||||
|
[matTooltip]="item.tooltip || ''"> |
||||
|
|
||||
|
<!-- Icon --> |
||||
|
<ng-container *ngIf="item.icon"> |
||||
|
<mat-icon |
||||
|
class="fuse-vertical-navigation-item-icon" |
||||
|
[ngClass]="item.classes?.icon" |
||||
|
[svgIcon]="item.icon"></mat-icon> |
||||
|
</ng-container> |
||||
|
|
||||
|
<!-- Title & Subtitle --> |
||||
|
<div class="fuse-vertical-navigation-item-title-wrapper"> |
||||
|
<div class="fuse-vertical-navigation-item-title"> |
||||
|
<span [ngClass]="item.classes?.title"> |
||||
|
{{item.title}} |
||||
|
</span> |
||||
|
</div> |
||||
|
<ng-container *ngIf="item.subtitle"> |
||||
|
<div class="fuse-vertical-navigation-item-subtitle"> |
||||
|
<span [ngClass]="item.classes?.subtitle"> |
||||
|
{{item.subtitle}} |
||||
|
</span> |
||||
|
</div> |
||||
|
</ng-container> |
||||
|
</div> |
||||
|
|
||||
|
<!-- Badge --> |
||||
|
<ng-container *ngIf="item.badge"> |
||||
|
<div class="fuse-vertical-navigation-item-badge"> |
||||
|
<div |
||||
|
class="fuse-vertical-navigation-item-badge-content" |
||||
|
[ngClass]="item.badge.classes"> |
||||
|
{{item.badge.title}} |
||||
|
</div> |
||||
|
</div> |
||||
|
</ng-container> |
||||
|
|
||||
|
</div> |
||||
|
|
||||
|
</div> |
||||
|
|
||||
|
<ng-container *ngIf="!skipChildren"> |
||||
|
|
||||
|
<div class="fuse-vertical-navigation-item-children"> |
||||
|
|
||||
|
<ng-container *ngFor="let item of item.children; trackBy: trackByFn"> |
||||
|
|
||||
|
<!-- Skip the hidden items --> |
||||
|
<ng-container *ngIf="(item.hidden && !item.hidden(item)) || !item.hidden"> |
||||
|
|
||||
|
<!-- Basic --> |
||||
|
<ng-container *ngIf="item.type === 'basic'"> |
||||
|
<fuse-vertical-navigation-basic-item |
||||
|
[item]="item" |
||||
|
[name]="name"></fuse-vertical-navigation-basic-item> |
||||
|
</ng-container> |
||||
|
|
||||
|
<!-- Collapsable --> |
||||
|
<ng-container *ngIf="item.type === 'collapsable'"> |
||||
|
<fuse-vertical-navigation-collapsable-item |
||||
|
[item]="item" |
||||
|
[name]="name" |
||||
|
[autoCollapse]="autoCollapse"></fuse-vertical-navigation-collapsable-item> |
||||
|
</ng-container> |
||||
|
|
||||
|
<!-- Divider --> |
||||
|
<ng-container *ngIf="item.type === 'divider'"> |
||||
|
<fuse-vertical-navigation-divider-item |
||||
|
[item]="item" |
||||
|
[name]="name"></fuse-vertical-navigation-divider-item> |
||||
|
</ng-container> |
||||
|
|
||||
|
<!-- Group --> |
||||
|
<ng-container *ngIf="item.type === 'group'"> |
||||
|
<fuse-vertical-navigation-group-item |
||||
|
[item]="item" |
||||
|
[name]="name"></fuse-vertical-navigation-group-item> |
||||
|
</ng-container> |
||||
|
|
||||
|
<!-- Spacer --> |
||||
|
<ng-container *ngIf="item.type === 'spacer'"> |
||||
|
<fuse-vertical-navigation-spacer-item |
||||
|
[item]="item" |
||||
|
[name]="name"></fuse-vertical-navigation-spacer-item> |
||||
|
</ng-container> |
||||
|
|
||||
|
</ng-container> |
||||
|
|
||||
|
</ng-container> |
||||
|
|
||||
|
</div> |
||||
|
|
||||
|
</ng-container> |
@ -0,0 +1,187 @@ |
|||||
|
import { ChangeDetectionStrategy, ChangeDetectorRef, Component, Input, OnChanges, OnDestroy, OnInit, SimpleChanges } from '@angular/core'; |
||||
|
import { NavigationEnd, Router } from '@angular/router'; |
||||
|
import { BooleanInput } from '@angular/cdk/coercion'; |
||||
|
import { Subject } from 'rxjs'; |
||||
|
import { filter, takeUntil } from 'rxjs/operators'; |
||||
|
import { FuseVerticalNavigationComponent } from '@fuse/components/navigation/vertical/vertical.component'; |
||||
|
import { FuseNavigationService } from '@fuse/components/navigation/navigation.service'; |
||||
|
import { FuseNavigationItem } from '@fuse/components/navigation/navigation.types'; |
||||
|
|
||||
|
@Component({ |
||||
|
selector : 'fuse-vertical-navigation-aside-item', |
||||
|
templateUrl : './aside.component.html', |
||||
|
changeDetection: ChangeDetectionStrategy.OnPush |
||||
|
}) |
||||
|
export class FuseVerticalNavigationAsideItemComponent implements OnChanges, OnInit, OnDestroy |
||||
|
{ |
||||
|
/* eslint-disable @typescript-eslint/naming-convention */ |
||||
|
static ngAcceptInputType_autoCollapse: BooleanInput; |
||||
|
static ngAcceptInputType_skipChildren: BooleanInput; |
||||
|
/* eslint-enable @typescript-eslint/naming-convention */ |
||||
|
|
||||
|
@Input() activeItemId: string; |
||||
|
@Input() autoCollapse: boolean; |
||||
|
@Input() item: FuseNavigationItem; |
||||
|
@Input() name: string; |
||||
|
@Input() skipChildren: boolean; |
||||
|
|
||||
|
active: boolean = false; |
||||
|
private _fuseVerticalNavigationComponent: FuseVerticalNavigationComponent; |
||||
|
private _unsubscribeAll: Subject<any> = new Subject<any>(); |
||||
|
|
||||
|
/** |
||||
|
* Constructor |
||||
|
*/ |
||||
|
constructor( |
||||
|
private _changeDetectorRef: ChangeDetectorRef, |
||||
|
private _router: Router, |
||||
|
private _fuseNavigationService: FuseNavigationService |
||||
|
) |
||||
|
{ |
||||
|
} |
||||
|
|
||||
|
// -----------------------------------------------------------------------------------------------------
|
||||
|
// @ Lifecycle hooks
|
||||
|
// -----------------------------------------------------------------------------------------------------
|
||||
|
|
||||
|
/** |
||||
|
* On changes |
||||
|
* |
||||
|
* @param changes |
||||
|
*/ |
||||
|
ngOnChanges(changes: SimpleChanges): void |
||||
|
{ |
||||
|
// Active item id
|
||||
|
if ( 'activeItemId' in changes ) |
||||
|
{ |
||||
|
// Mark if active
|
||||
|
this._markIfActive(this._router.url); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* On init |
||||
|
*/ |
||||
|
ngOnInit(): void |
||||
|
{ |
||||
|
// Mark if active
|
||||
|
this._markIfActive(this._router.url); |
||||
|
|
||||
|
// Attach a listener to the NavigationEnd event
|
||||
|
this._router.events |
||||
|
.pipe( |
||||
|
filter((event): event is NavigationEnd => event instanceof NavigationEnd), |
||||
|
takeUntil(this._unsubscribeAll) |
||||
|
) |
||||
|
.subscribe((event: NavigationEnd) => { |
||||
|
|
||||
|
// Mark if active
|
||||
|
this._markIfActive(event.urlAfterRedirects); |
||||
|
}); |
||||
|
|
||||
|
// Get the parent navigation component
|
||||
|
this._fuseVerticalNavigationComponent = this._fuseNavigationService.getComponent(this.name); |
||||
|
|
||||
|
// Subscribe to onRefreshed on the navigation component
|
||||
|
this._fuseVerticalNavigationComponent.onRefreshed.pipe( |
||||
|
takeUntil(this._unsubscribeAll) |
||||
|
).subscribe(() => { |
||||
|
|
||||
|
// Mark for check
|
||||
|
this._changeDetectorRef.markForCheck(); |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* On destroy |
||||
|
*/ |
||||
|
ngOnDestroy(): void |
||||
|
{ |
||||
|
// Unsubscribe from all subscriptions
|
||||
|
this._unsubscribeAll.next(); |
||||
|
this._unsubscribeAll.complete(); |
||||
|
} |
||||
|
|
||||
|
// -----------------------------------------------------------------------------------------------------
|
||||
|
// @ Public methods
|
||||
|
// -----------------------------------------------------------------------------------------------------
|
||||
|
|
||||
|
/** |
||||
|
* Track by function for ngFor loops |
||||
|
* |
||||
|
* @param index |
||||
|
* @param item |
||||
|
*/ |
||||
|
trackByFn(index: number, item: any): any |
||||
|
{ |
||||
|
return item.id || index; |
||||
|
} |
||||
|
|
||||
|
// -----------------------------------------------------------------------------------------------------
|
||||
|
// @ Private methods
|
||||
|
// -----------------------------------------------------------------------------------------------------
|
||||
|
|
||||
|
/** |
||||
|
* Check if the given item has the given url |
||||
|
* in one of its children |
||||
|
* |
||||
|
* @param item |
||||
|
* @param currentUrl |
||||
|
* @private |
||||
|
*/ |
||||
|
private _hasActiveChild(item: FuseNavigationItem, currentUrl: string): boolean |
||||
|
{ |
||||
|
const children = item.children; |
||||
|
|
||||
|
if ( !children ) |
||||
|
{ |
||||
|
return false; |
||||
|
} |
||||
|
|
||||
|
for ( const child of children ) |
||||
|
{ |
||||
|
if ( child.children ) |
||||
|
{ |
||||
|
if ( this._hasActiveChild(child, currentUrl) ) |
||||
|
{ |
||||
|
return true; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
// Skip items other than 'basic'
|
||||
|
if ( child.type !== 'basic' ) |
||||
|
{ |
||||
|
continue; |
||||
|
} |
||||
|
|
||||
|
// Check if the child has a link and is active
|
||||
|
if ( child.link && this._router.isActive(child.link, child.exactMatch || false) ) |
||||
|
{ |
||||
|
return true; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
return false; |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* Decide and mark if the item is active |
||||
|
* |
||||
|
* @private |
||||
|
*/ |
||||
|
private _markIfActive(currentUrl: string): void |
||||
|
{ |
||||
|
// Check if the activeItemId is equals to this item id
|
||||
|
this.active = this.activeItemId === this.item.id; |
||||
|
|
||||
|
// If the aside has a children that is active,
|
||||
|
// always mark it as active
|
||||
|
if ( this._hasActiveChild(this.item, currentUrl) ) |
||||
|
{ |
||||
|
this.active = true; |
||||
|
} |
||||
|
|
||||
|
// Mark for check
|
||||
|
this._changeDetectorRef.markForCheck(); |
||||
|
} |
||||
|
} |
@ -0,0 +1,127 @@ |
|||||
|
<!-- Item wrapper --> |
||||
|
<div |
||||
|
class="fuse-vertical-navigation-item-wrapper" |
||||
|
[class.fuse-vertical-navigation-item-has-subtitle]="!!item.subtitle" |
||||
|
[ngClass]="item.classes?.wrapper"> |
||||
|
|
||||
|
<!-- Item with an internal link --> |
||||
|
<ng-container *ngIf="item.link && !item.externalLink && !item.function && !item.disabled"> |
||||
|
<a |
||||
|
class="fuse-vertical-navigation-item" |
||||
|
[ngClass]="{'fuse-vertical-navigation-item-active-forced': item.active}" |
||||
|
[routerLink]="[item.link]" |
||||
|
[routerLinkActive]="'fuse-vertical-navigation-item-active'" |
||||
|
[routerLinkActiveOptions]="isActiveMatchOptions" |
||||
|
[matTooltip]="item.tooltip || ''"> |
||||
|
<ng-container *ngTemplateOutlet="itemTemplate"></ng-container> |
||||
|
</a> |
||||
|
</ng-container> |
||||
|
|
||||
|
<!-- Item with an external link --> |
||||
|
<ng-container *ngIf="item.link && item.externalLink && !item.function && !item.disabled"> |
||||
|
<a |
||||
|
class="fuse-vertical-navigation-item" |
||||
|
[href]="item.link" |
||||
|
[target]="item.target || '_self'" |
||||
|
[matTooltip]="item.tooltip || ''"> |
||||
|
<ng-container *ngTemplateOutlet="itemTemplate"></ng-container> |
||||
|
</a> |
||||
|
</ng-container> |
||||
|
|
||||
|
<!-- Item with a function --> |
||||
|
<ng-container *ngIf="!item.link && item.function && !item.disabled"> |
||||
|
<div |
||||
|
class="fuse-vertical-navigation-item" |
||||
|
[ngClass]="{'fuse-vertical-navigation-item-active-forced': item.active}" |
||||
|
[matTooltip]="item.tooltip || ''" |
||||
|
(click)="item.function(item)"> |
||||
|
<ng-container *ngTemplateOutlet="itemTemplate"></ng-container> |
||||
|
</div> |
||||
|
</ng-container> |
||||
|
|
||||
|
<!-- Item with an internal link and function --> |
||||
|
<ng-container *ngIf="item.link && !item.externalLink && item.function && !item.disabled"> |
||||
|
<a |
||||
|
class="fuse-vertical-navigation-item" |
||||
|
[ngClass]="{'fuse-vertical-navigation-item-active-forced': item.active}" |
||||
|
[routerLink]="[item.link]" |
||||
|
[routerLinkActive]="'fuse-vertical-navigation-item-active'" |
||||
|
[routerLinkActiveOptions]="isActiveMatchOptions" |
||||
|
[matTooltip]="item.tooltip || ''" |
||||
|
(click)="item.function(item)"> |
||||
|
<ng-container *ngTemplateOutlet="itemTemplate"></ng-container> |
||||
|
</a> |
||||
|
</ng-container> |
||||
|
|
||||
|
<!-- Item with an external link and function --> |
||||
|
<ng-container *ngIf="item.link && item.externalLink && item.function && !item.disabled"> |
||||
|
<a |
||||
|
class="fuse-vertical-navigation-item" |
||||
|
[href]="item.link" |
||||
|
[target]="item.target || '_self'" |
||||
|
[matTooltip]="item.tooltip || ''" |
||||
|
(click)="item.function(item)"> |
||||
|
<ng-container *ngTemplateOutlet="itemTemplate"></ng-container> |
||||
|
</a> |
||||
|
</ng-container> |
||||
|
|
||||
|
<!-- Item with a no link and no function --> |
||||
|
<ng-container *ngIf="!item.link && !item.function && !item.disabled"> |
||||
|
<div |
||||
|
class="fuse-vertical-navigation-item" |
||||
|
[ngClass]="{'fuse-vertical-navigation-item-active-forced': item.active}" |
||||
|
[matTooltip]="item.tooltip || ''"> |
||||
|
<ng-container *ngTemplateOutlet="itemTemplate"></ng-container> |
||||
|
</div> |
||||
|
</ng-container> |
||||
|
|
||||
|
<!-- Item is disabled --> |
||||
|
<ng-container *ngIf="item.disabled"> |
||||
|
<div |
||||
|
class="fuse-vertical-navigation-item fuse-vertical-navigation-item-disabled" |
||||
|
[matTooltip]="item.tooltip || ''"> |
||||
|
<ng-container *ngTemplateOutlet="itemTemplate"></ng-container> |
||||
|
</div> |
||||
|
</ng-container> |
||||
|
|
||||
|
</div> |
||||
|
|
||||
|
<!-- Item template --> |
||||
|
<ng-template #itemTemplate> |
||||
|
|
||||
|
<!-- Icon --> |
||||
|
<ng-container *ngIf="item.icon"> |
||||
|
<mat-icon |
||||
|
class="fuse-vertical-navigation-item-icon" |
||||
|
[ngClass]="item.classes?.icon" |
||||
|
[svgIcon]="item.icon"></mat-icon> |
||||
|
</ng-container> |
||||
|
|
||||
|
<!-- Title & Subtitle --> |
||||
|
<div class="fuse-vertical-navigation-item-title-wrapper"> |
||||
|
<div class="fuse-vertical-navigation-item-title"> |
||||
|
<span [ngClass]="item.classes?.title"> |
||||
|
{{item.title}} |
||||
|
</span> |
||||
|
</div> |
||||
|
<ng-container *ngIf="item.subtitle"> |
||||
|
<div class="fuse-vertical-navigation-item-subtitle"> |
||||
|
<span [ngClass]="item.classes?.subtitle"> |
||||
|
{{item.subtitle}} |
||||
|
</span> |
||||
|
</div> |
||||
|
</ng-container> |
||||
|
</div> |
||||
|
|
||||
|
<!-- Badge --> |
||||
|
<ng-container *ngIf="item.badge"> |
||||
|
<div class="fuse-vertical-navigation-item-badge"> |
||||
|
<div |
||||
|
class="fuse-vertical-navigation-item-badge-content" |
||||
|
[ngClass]="item.badge.classes"> |
||||
|
{{item.badge.title}} |
||||
|
</div> |
||||
|
</div> |
||||
|
</ng-container> |
||||
|
|
||||
|
</ng-template> |
@ -0,0 +1,82 @@ |
|||||
|
import { ChangeDetectionStrategy, ChangeDetectorRef, Component, Input, OnDestroy, OnInit } from '@angular/core'; |
||||
|
import { IsActiveMatchOptions } from '@angular/router'; |
||||
|
import { Subject } from 'rxjs'; |
||||
|
import { takeUntil } from 'rxjs/operators'; |
||||
|
import { FuseVerticalNavigationComponent } from '@fuse/components/navigation/vertical/vertical.component'; |
||||
|
import { FuseNavigationService } from '@fuse/components/navigation/navigation.service'; |
||||
|
import { FuseNavigationItem } from '@fuse/components/navigation/navigation.types'; |
||||
|
import { FuseUtilsService } from '@fuse/services/utils/utils.service'; |
||||
|
|
||||
|
@Component({ |
||||
|
selector : 'fuse-vertical-navigation-basic-item', |
||||
|
templateUrl : './basic.component.html', |
||||
|
changeDetection: ChangeDetectionStrategy.OnPush |
||||
|
}) |
||||
|
export class FuseVerticalNavigationBasicItemComponent implements OnInit, OnDestroy |
||||
|
{ |
||||
|
@Input() item: FuseNavigationItem; |
||||
|
@Input() name: string; |
||||
|
|
||||
|
isActiveMatchOptions: IsActiveMatchOptions; |
||||
|
private _fuseVerticalNavigationComponent: FuseVerticalNavigationComponent; |
||||
|
private _unsubscribeAll: Subject<any> = new Subject<any>(); |
||||
|
|
||||
|
/** |
||||
|
* Constructor |
||||
|
*/ |
||||
|
constructor( |
||||
|
private _changeDetectorRef: ChangeDetectorRef, |
||||
|
private _fuseNavigationService: FuseNavigationService, |
||||
|
private _fuseUtilsService: FuseUtilsService |
||||
|
) |
||||
|
{ |
||||
|
// Set the equivalent of {exact: false} as default for active match options.
|
||||
|
// We are not assigning the item.isActiveMatchOptions directly to the
|
||||
|
// [routerLinkActiveOptions] because if it's "undefined" initially, the router
|
||||
|
// will throw an error and stop working.
|
||||
|
this.isActiveMatchOptions = this._fuseUtilsService.subsetMatchOptions; |
||||
|
} |
||||
|
|
||||
|
// -----------------------------------------------------------------------------------------------------
|
||||
|
// @ Lifecycle hooks
|
||||
|
// -----------------------------------------------------------------------------------------------------
|
||||
|
|
||||
|
/** |
||||
|
* On init |
||||
|
*/ |
||||
|
ngOnInit(): void |
||||
|
{ |
||||
|
// Set the "isActiveMatchOptions" either from item's
|
||||
|
// "isActiveMatchOptions" or the equivalent form of
|
||||
|
// item's "exactMatch" option
|
||||
|
this.isActiveMatchOptions = |
||||
|
this.item.isActiveMatchOptions ?? this.item.exactMatch |
||||
|
? this._fuseUtilsService.exactMatchOptions |
||||
|
: this._fuseUtilsService.subsetMatchOptions; |
||||
|
|
||||
|
// Get the parent navigation component
|
||||
|
this._fuseVerticalNavigationComponent = this._fuseNavigationService.getComponent(this.name); |
||||
|
|
||||
|
// Mark for check
|
||||
|
this._changeDetectorRef.markForCheck(); |
||||
|
|
||||
|
// Subscribe to onRefreshed on the navigation component
|
||||
|
this._fuseVerticalNavigationComponent.onRefreshed.pipe( |
||||
|
takeUntil(this._unsubscribeAll) |
||||
|
).subscribe(() => { |
||||
|
|
||||
|
// Mark for check
|
||||
|
this._changeDetectorRef.markForCheck(); |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* On destroy |
||||
|
*/ |
||||
|
ngOnDestroy(): void |
||||
|
{ |
||||
|
// Unsubscribe from all subscriptions
|
||||
|
this._unsubscribeAll.next(); |
||||
|
this._unsubscribeAll.complete(); |
||||
|
} |
||||
|
} |
@ -0,0 +1,106 @@ |
|||||
|
<div |
||||
|
class="fuse-vertical-navigation-item-wrapper" |
||||
|
[class.fuse-vertical-navigation-item-has-subtitle]="!!item.subtitle" |
||||
|
[ngClass]="item.classes?.wrapper"> |
||||
|
|
||||
|
<div |
||||
|
class="fuse-vertical-navigation-item" |
||||
|
[ngClass]="{'fuse-vertical-navigation-item-disabled': item.disabled}" |
||||
|
[matTooltip]="item.tooltip || ''" |
||||
|
(click)="toggleCollapsable()"> |
||||
|
|
||||
|
<!-- Icon --> |
||||
|
<ng-container *ngIf="item.icon"> |
||||
|
<mat-icon |
||||
|
class="fuse-vertical-navigation-item-icon" |
||||
|
[ngClass]="item.classes?.icon" |
||||
|
[svgIcon]="item.icon"></mat-icon> |
||||
|
</ng-container> |
||||
|
|
||||
|
<!-- Title & Subtitle --> |
||||
|
<div class="fuse-vertical-navigation-item-title-wrapper"> |
||||
|
<div class="fuse-vertical-navigation-item-title"> |
||||
|
<span [ngClass]="item.classes?.title"> |
||||
|
{{item.title}} |
||||
|
</span> |
||||
|
</div> |
||||
|
<ng-container *ngIf="item.subtitle"> |
||||
|
<div class="fuse-vertical-navigation-item-subtitle"> |
||||
|
<span [ngClass]="item.classes?.subtitle"> |
||||
|
{{item.subtitle}} |
||||
|
</span> |
||||
|
</div> |
||||
|
</ng-container> |
||||
|
</div> |
||||
|
|
||||
|
<!-- Badge --> |
||||
|
<ng-container *ngIf="item.badge"> |
||||
|
<div class="fuse-vertical-navigation-item-badge"> |
||||
|
<div |
||||
|
class="fuse-vertical-navigation-item-badge-content" |
||||
|
[ngClass]="item.badge.classes"> |
||||
|
{{item.badge.title}} |
||||
|
</div> |
||||
|
</div> |
||||
|
</ng-container> |
||||
|
|
||||
|
<!-- Arrow --> |
||||
|
<mat-icon |
||||
|
class="fuse-vertical-navigation-item-arrow icon-size-4" |
||||
|
[svgIcon]="'heroicons_solid:chevron-right'"></mat-icon> |
||||
|
|
||||
|
</div> |
||||
|
|
||||
|
</div> |
||||
|
|
||||
|
<div |
||||
|
class="fuse-vertical-navigation-item-children" |
||||
|
*ngIf="!isCollapsed" |
||||
|
@expandCollapse> |
||||
|
|
||||
|
<ng-container *ngFor="let item of item.children; trackBy: trackByFn"> |
||||
|
|
||||
|
<!-- Skip the hidden items --> |
||||
|
<ng-container *ngIf="(item.hidden && !item.hidden(item)) || !item.hidden"> |
||||
|
|
||||
|
<!-- Basic --> |
||||
|
<ng-container *ngIf="item.type === 'basic'"> |
||||
|
<fuse-vertical-navigation-basic-item |
||||
|
[item]="item" |
||||
|
[name]="name"></fuse-vertical-navigation-basic-item> |
||||
|
</ng-container> |
||||
|
|
||||
|
<!-- Collapsable --> |
||||
|
<ng-container *ngIf="item.type === 'collapsable'"> |
||||
|
<fuse-vertical-navigation-collapsable-item |
||||
|
[item]="item" |
||||
|
[name]="name" |
||||
|
[autoCollapse]="autoCollapse"></fuse-vertical-navigation-collapsable-item> |
||||
|
</ng-container> |
||||
|
|
||||
|
<!-- Divider --> |
||||
|
<ng-container *ngIf="item.type === 'divider'"> |
||||
|
<fuse-vertical-navigation-divider-item |
||||
|
[item]="item" |
||||
|
[name]="name"></fuse-vertical-navigation-divider-item> |
||||
|
</ng-container> |
||||
|
|
||||
|
<!-- Group --> |
||||
|
<ng-container *ngIf="item.type === 'group'"> |
||||
|
<fuse-vertical-navigation-group-item |
||||
|
[item]="item" |
||||
|
[name]="name"></fuse-vertical-navigation-group-item> |
||||
|
</ng-container> |
||||
|
|
||||
|
<!-- Spacer --> |
||||
|
<ng-container *ngIf="item.type === 'spacer'"> |
||||
|
<fuse-vertical-navigation-spacer-item |
||||
|
[item]="item" |
||||
|
[name]="name"></fuse-vertical-navigation-spacer-item> |
||||
|
</ng-container> |
||||
|
|
||||
|
</ng-container> |
||||
|
|
||||
|
</ng-container> |
||||
|
|
||||
|
</div> |
@ -0,0 +1,346 @@ |
|||||
|
import { ChangeDetectionStrategy, ChangeDetectorRef, Component, HostBinding, Input, OnDestroy, OnInit } from '@angular/core'; |
||||
|
import { NavigationEnd, Router } from '@angular/router'; |
||||
|
import { BooleanInput } from '@angular/cdk/coercion'; |
||||
|
import { Subject } from 'rxjs'; |
||||
|
import { filter, takeUntil } from 'rxjs/operators'; |
||||
|
import { fuseAnimations } from '@fuse/animations'; |
||||
|
import { FuseVerticalNavigationComponent } from '@fuse/components/navigation/vertical/vertical.component'; |
||||
|
import { FuseNavigationService } from '@fuse/components/navigation/navigation.service'; |
||||
|
import { FuseNavigationItem } from '@fuse/components/navigation/navigation.types'; |
||||
|
|
||||
|
@Component({ |
||||
|
selector : 'fuse-vertical-navigation-collapsable-item', |
||||
|
templateUrl : './collapsable.component.html', |
||||
|
animations : fuseAnimations, |
||||
|
changeDetection: ChangeDetectionStrategy.OnPush |
||||
|
}) |
||||
|
export class FuseVerticalNavigationCollapsableItemComponent implements OnInit, OnDestroy |
||||
|
{ |
||||
|
/* eslint-disable @typescript-eslint/naming-convention */ |
||||
|
static ngAcceptInputType_autoCollapse: BooleanInput; |
||||
|
/* eslint-enable @typescript-eslint/naming-convention */ |
||||
|
|
||||
|
@Input() autoCollapse: boolean; |
||||
|
@Input() item: FuseNavigationItem; |
||||
|
@Input() name: string; |
||||
|
|
||||
|
isCollapsed: boolean = true; |
||||
|
isExpanded: boolean = false; |
||||
|
private _fuseVerticalNavigationComponent: FuseVerticalNavigationComponent; |
||||
|
private _unsubscribeAll: Subject<any> = new Subject<any>(); |
||||
|
|
||||
|
/** |
||||
|
* Constructor |
||||
|
*/ |
||||
|
constructor( |
||||
|
private _changeDetectorRef: ChangeDetectorRef, |
||||
|
private _router: Router, |
||||
|
private _fuseNavigationService: FuseNavigationService |
||||
|
) |
||||
|
{ |
||||
|
} |
||||
|
|
||||
|
// -----------------------------------------------------------------------------------------------------
|
||||
|
// @ Accessors
|
||||
|
// -----------------------------------------------------------------------------------------------------
|
||||
|
|
||||
|
/** |
||||
|
* Host binding for component classes |
||||
|
*/ |
||||
|
@HostBinding('class') get classList(): any |
||||
|
{ |
||||
|
return { |
||||
|
'fuse-vertical-navigation-item-collapsed': this.isCollapsed, |
||||
|
'fuse-vertical-navigation-item-expanded' : this.isExpanded |
||||
|
}; |
||||
|
} |
||||
|
|
||||
|
// -----------------------------------------------------------------------------------------------------
|
||||
|
// @ Lifecycle hooks
|
||||
|
// -----------------------------------------------------------------------------------------------------
|
||||
|
|
||||
|
/** |
||||
|
* On init |
||||
|
*/ |
||||
|
ngOnInit(): void |
||||
|
{ |
||||
|
// Get the parent navigation component
|
||||
|
this._fuseVerticalNavigationComponent = this._fuseNavigationService.getComponent(this.name); |
||||
|
|
||||
|
// If the item has a children that has a matching url with the current url, expand...
|
||||
|
if ( this._hasActiveChild(this.item, this._router.url) ) |
||||
|
{ |
||||
|
this.expand(); |
||||
|
} |
||||
|
// Otherwise...
|
||||
|
else |
||||
|
{ |
||||
|
// If the autoCollapse is on, collapse...
|
||||
|
if ( this.autoCollapse ) |
||||
|
{ |
||||
|
this.collapse(); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
// Listen for the onCollapsableItemCollapsed from the service
|
||||
|
this._fuseVerticalNavigationComponent.onCollapsableItemCollapsed |
||||
|
.pipe(takeUntil(this._unsubscribeAll)) |
||||
|
.subscribe((collapsedItem) => { |
||||
|
|
||||
|
// Check if the collapsed item is null
|
||||
|
if ( collapsedItem === null ) |
||||
|
{ |
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
// Collapse if this is a children of the collapsed item
|
||||
|
if ( this._isChildrenOf(collapsedItem, this.item) ) |
||||
|
{ |
||||
|
this.collapse(); |
||||
|
} |
||||
|
}); |
||||
|
|
||||
|
// Listen for the onCollapsableItemExpanded from the service if the autoCollapse is on
|
||||
|
if ( this.autoCollapse ) |
||||
|
{ |
||||
|
this._fuseVerticalNavigationComponent.onCollapsableItemExpanded |
||||
|
.pipe(takeUntil(this._unsubscribeAll)) |
||||
|
.subscribe((expandedItem) => { |
||||
|
|
||||
|
// Check if the expanded item is null
|
||||
|
if ( expandedItem === null ) |
||||
|
{ |
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
// Check if this is a parent of the expanded item
|
||||
|
if ( this._isChildrenOf(this.item, expandedItem) ) |
||||
|
{ |
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
// Check if this has a children with a matching url with the current active url
|
||||
|
if ( this._hasActiveChild(this.item, this._router.url) ) |
||||
|
{ |
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
// Check if this is the expanded item
|
||||
|
if ( this.item === expandedItem ) |
||||
|
{ |
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
// If none of the above conditions are matched, collapse this item
|
||||
|
this.collapse(); |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
// Attach a listener to the NavigationEnd event
|
||||
|
this._router.events |
||||
|
.pipe( |
||||
|
filter((event): event is NavigationEnd => event instanceof NavigationEnd), |
||||
|
takeUntil(this._unsubscribeAll) |
||||
|
) |
||||
|
.subscribe((event: NavigationEnd) => { |
||||
|
|
||||
|
// If the item has a children that has a matching url with the current url, expand...
|
||||
|
if ( this._hasActiveChild(this.item, event.urlAfterRedirects) ) |
||||
|
{ |
||||
|
this.expand(); |
||||
|
} |
||||
|
// Otherwise...
|
||||
|
else |
||||
|
{ |
||||
|
// If the autoCollapse is on, collapse...
|
||||
|
if ( this.autoCollapse ) |
||||
|
{ |
||||
|
this.collapse(); |
||||
|
} |
||||
|
} |
||||
|
}); |
||||
|
|
||||
|
// Subscribe to onRefreshed on the navigation component
|
||||
|
this._fuseVerticalNavigationComponent.onRefreshed.pipe( |
||||
|
takeUntil(this._unsubscribeAll) |
||||
|
).subscribe(() => { |
||||
|
|
||||
|
// Mark for check
|
||||
|
this._changeDetectorRef.markForCheck(); |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* On destroy |
||||
|
*/ |
||||
|
ngOnDestroy(): void |
||||
|
{ |
||||
|
// Unsubscribe from all subscriptions
|
||||
|
this._unsubscribeAll.next(); |
||||
|
this._unsubscribeAll.complete(); |
||||
|
} |
||||
|
|
||||
|
// -----------------------------------------------------------------------------------------------------
|
||||
|
// @ Public methods
|
||||
|
// -----------------------------------------------------------------------------------------------------
|
||||
|
|
||||
|
/** |
||||
|
* Collapse |
||||
|
*/ |
||||
|
collapse(): void |
||||
|
{ |
||||
|
// Return if the item is disabled
|
||||
|
if ( this.item.disabled ) |
||||
|
{ |
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
// Return if the item is already collapsed
|
||||
|
if ( this.isCollapsed ) |
||||
|
{ |
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
// Collapse it
|
||||
|
this.isCollapsed = true; |
||||
|
this.isExpanded = !this.isCollapsed; |
||||
|
|
||||
|
// Mark for check
|
||||
|
this._changeDetectorRef.markForCheck(); |
||||
|
|
||||
|
// Execute the observable
|
||||
|
this._fuseVerticalNavigationComponent.onCollapsableItemCollapsed.next(this.item); |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* Expand |
||||
|
*/ |
||||
|
expand(): void |
||||
|
{ |
||||
|
// Return if the item is disabled
|
||||
|
if ( this.item.disabled ) |
||||
|
{ |
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
// Return if the item is already expanded
|
||||
|
if ( !this.isCollapsed ) |
||||
|
{ |
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
// Expand it
|
||||
|
this.isCollapsed = false; |
||||
|
this.isExpanded = !this.isCollapsed; |
||||
|
|
||||
|
// Mark for check
|
||||
|
this._changeDetectorRef.markForCheck(); |
||||
|
|
||||
|
// Execute the observable
|
||||
|
this._fuseVerticalNavigationComponent.onCollapsableItemExpanded.next(this.item); |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* Toggle collapsable |
||||
|
*/ |
||||
|
toggleCollapsable(): void |
||||
|
{ |
||||
|
// Toggle collapse/expand
|
||||
|
if ( this.isCollapsed ) |
||||
|
{ |
||||
|
this.expand(); |
||||
|
} |
||||
|
else |
||||
|
{ |
||||
|
this.collapse(); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* Track by function for ngFor loops |
||||
|
* |
||||
|
* @param index |
||||
|
* @param item |
||||
|
*/ |
||||
|
trackByFn(index: number, item: any): any |
||||
|
{ |
||||
|
return item.id || index; |
||||
|
} |
||||
|
|
||||
|
// -----------------------------------------------------------------------------------------------------
|
||||
|
// @ Private methods
|
||||
|
// -----------------------------------------------------------------------------------------------------
|
||||
|
|
||||
|
/** |
||||
|
* Check if the given item has the given url |
||||
|
* in one of its children |
||||
|
* |
||||
|
* @param item |
||||
|
* @param currentUrl |
||||
|
* @private |
||||
|
*/ |
||||
|
private _hasActiveChild(item: FuseNavigationItem, currentUrl: string): boolean |
||||
|
{ |
||||
|
const children = item.children; |
||||
|
|
||||
|
if ( !children ) |
||||
|
{ |
||||
|
return false; |
||||
|
} |
||||
|
|
||||
|
for ( const child of children ) |
||||
|
{ |
||||
|
if ( child.children ) |
||||
|
{ |
||||
|
if ( this._hasActiveChild(child, currentUrl) ) |
||||
|
{ |
||||
|
return true; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
// Check if the child has a link and is active
|
||||
|
if ( child.link && this._router.isActive(child.link, child.exactMatch || false) ) |
||||
|
{ |
||||
|
return true; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
return false; |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* Check if this is a children |
||||
|
* of the given item |
||||
|
* |
||||
|
* @param parent |
||||
|
* @param item |
||||
|
* @private |
||||
|
*/ |
||||
|
private _isChildrenOf(parent: FuseNavigationItem, item: FuseNavigationItem): boolean |
||||
|
{ |
||||
|
const children = parent.children; |
||||
|
|
||||
|
if ( !children ) |
||||
|
{ |
||||
|
return false; |
||||
|
} |
||||
|
|
||||
|
if ( children.indexOf(item) > -1 ) |
||||
|
{ |
||||
|
return true; |
||||
|
} |
||||
|
|
||||
|
for ( const child of children ) |
||||
|
{ |
||||
|
if ( child.children ) |
||||
|
{ |
||||
|
if ( this._isChildrenOf(child, item) ) |
||||
|
{ |
||||
|
return true; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
return false; |
||||
|
} |
||||
|
} |
@ -0,0 +1,4 @@ |
|||||
|
<!-- Divider --> |
||||
|
<div |
||||
|
class="fuse-vertical-navigation-item-wrapper divider" |
||||
|
[ngClass]="item.classes?.wrapper"></div> |
@ -0,0 +1,62 @@ |
|||||
|
import { ChangeDetectionStrategy, ChangeDetectorRef, Component, Input, OnDestroy, OnInit } from '@angular/core'; |
||||
|
import { Subject } from 'rxjs'; |
||||
|
import { takeUntil } from 'rxjs/operators'; |
||||
|
import { FuseVerticalNavigationComponent } from '@fuse/components/navigation/vertical/vertical.component'; |
||||
|
import { FuseNavigationService } from '@fuse/components/navigation/navigation.service'; |
||||
|
import { FuseNavigationItem } from '@fuse/components/navigation/navigation.types'; |
||||
|
|
||||
|
@Component({ |
||||
|
selector : 'fuse-vertical-navigation-divider-item', |
||||
|
templateUrl : './divider.component.html', |
||||
|
changeDetection: ChangeDetectionStrategy.OnPush |
||||
|
}) |
||||
|
export class FuseVerticalNavigationDividerItemComponent implements OnInit, OnDestroy |
||||
|
{ |
||||
|
@Input() item: FuseNavigationItem; |
||||
|
@Input() name: string; |
||||
|
|
||||
|
private _fuseVerticalNavigationComponent: FuseVerticalNavigationComponent; |
||||
|
private _unsubscribeAll: Subject<any> = new Subject<any>(); |
||||
|
|
||||
|
/** |
||||
|
* Constructor |
||||
|
*/ |
||||
|
constructor( |
||||
|
private _changeDetectorRef: ChangeDetectorRef, |
||||
|
private _fuseNavigationService: FuseNavigationService |
||||
|
) |
||||
|
{ |
||||
|
} |
||||
|
|
||||
|
// -----------------------------------------------------------------------------------------------------
|
||||
|
// @ Lifecycle hooks
|
||||
|
// -----------------------------------------------------------------------------------------------------
|
||||
|
|
||||
|
/** |
||||
|
* On init |
||||
|
*/ |
||||
|
ngOnInit(): void |
||||
|
{ |
||||
|
// Get the parent navigation component
|
||||
|
this._fuseVerticalNavigationComponent = this._fuseNavigationService.getComponent(this.name); |
||||
|
|
||||
|
// Subscribe to onRefreshed on the navigation component
|
||||
|
this._fuseVerticalNavigationComponent.onRefreshed.pipe( |
||||
|
takeUntil(this._unsubscribeAll) |
||||
|
).subscribe(() => { |
||||
|
|
||||
|
// Mark for check
|
||||
|
this._changeDetectorRef.markForCheck(); |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* On destroy |
||||
|
*/ |
||||
|
ngOnDestroy(): void |
||||
|
{ |
||||
|
// Unsubscribe from all subscriptions
|
||||
|
this._unsubscribeAll.next(); |
||||
|
this._unsubscribeAll.complete(); |
||||
|
} |
||||
|
} |
@ -0,0 +1,91 @@ |
|||||
|
<!-- Item wrapper --> |
||||
|
<div |
||||
|
class="fuse-vertical-navigation-item-wrapper" |
||||
|
[class.fuse-vertical-navigation-item-has-subtitle]="!!item.subtitle" |
||||
|
[ngClass]="item.classes?.wrapper"> |
||||
|
|
||||
|
<div class="fuse-vertical-navigation-item"> |
||||
|
|
||||
|
<!-- Icon --> |
||||
|
<ng-container *ngIf="item.icon"> |
||||
|
<mat-icon |
||||
|
class="fuse-vertical-navigation-item-icon" |
||||
|
[ngClass]="item.classes?.icon" |
||||
|
[svgIcon]="item.icon"></mat-icon> |
||||
|
</ng-container> |
||||
|
|
||||
|
<!-- Title & Subtitle --> |
||||
|
<div class="fuse-vertical-navigation-item-title-wrapper"> |
||||
|
<div class="fuse-vertical-navigation-item-title"> |
||||
|
<span [ngClass]="item.classes?.title"> |
||||
|
{{item.title}} |
||||
|
</span> |
||||
|
</div> |
||||
|
<ng-container *ngIf="item.subtitle"> |
||||
|
<div class="fuse-vertical-navigation-item-subtitle"> |
||||
|
<span [ngClass]="item.classes?.subtitle"> |
||||
|
{{item.subtitle}} |
||||
|
</span> |
||||
|
</div> |
||||
|
</ng-container> |
||||
|
</div> |
||||
|
|
||||
|
<!-- Badge --> |
||||
|
<ng-container *ngIf="item.badge"> |
||||
|
<div class="fuse-vertical-navigation-item-badge"> |
||||
|
<div |
||||
|
class="fuse-vertical-navigation-item-badge-content" |
||||
|
[ngClass]="item.badge.classes"> |
||||
|
{{item.badge.title}} |
||||
|
</div> |
||||
|
</div> |
||||
|
</ng-container> |
||||
|
|
||||
|
</div> |
||||
|
|
||||
|
</div> |
||||
|
|
||||
|
<ng-container *ngFor="let item of item.children; trackBy: trackByFn"> |
||||
|
|
||||
|
<!-- Skip the hidden items --> |
||||
|
<ng-container *ngIf="(item.hidden && !item.hidden(item)) || !item.hidden"> |
||||
|
|
||||
|
<!-- Basic --> |
||||
|
<ng-container *ngIf="item.type === 'basic'"> |
||||
|
<fuse-vertical-navigation-basic-item |
||||
|
[item]="item" |
||||
|
[name]="name"></fuse-vertical-navigation-basic-item> |
||||
|
</ng-container> |
||||
|
|
||||
|
<!-- Collapsable --> |
||||
|
<ng-container *ngIf="item.type === 'collapsable'"> |
||||
|
<fuse-vertical-navigation-collapsable-item |
||||
|
[item]="item" |
||||
|
[name]="name" |
||||
|
[autoCollapse]="autoCollapse"></fuse-vertical-navigation-collapsable-item> |
||||
|
</ng-container> |
||||
|
|
||||
|
<!-- Divider --> |
||||
|
<ng-container *ngIf="item.type === 'divider'"> |
||||
|
<fuse-vertical-navigation-divider-item |
||||
|
[item]="item" |
||||
|
[name]="name"></fuse-vertical-navigation-divider-item> |
||||
|
</ng-container> |
||||
|
|
||||
|
<!-- Group --> |
||||
|
<ng-container *ngIf="item.type === 'group'"> |
||||
|
<fuse-vertical-navigation-group-item |
||||
|
[item]="item" |
||||
|
[name]="name"></fuse-vertical-navigation-group-item> |
||||
|
</ng-container> |
||||
|
|
||||
|
<!-- Spacer --> |
||||
|
<ng-container *ngIf="item.type === 'spacer'"> |
||||
|
<fuse-vertical-navigation-spacer-item |
||||
|
[item]="item" |
||||
|
[name]="name"></fuse-vertical-navigation-spacer-item> |
||||
|
</ng-container> |
||||
|
|
||||
|
</ng-container> |
||||
|
|
||||
|
</ng-container> |
@ -0,0 +1,83 @@ |
|||||
|
import { ChangeDetectionStrategy, ChangeDetectorRef, Component, Input, OnDestroy, OnInit } from '@angular/core'; |
||||
|
import { BooleanInput } from '@angular/cdk/coercion'; |
||||
|
import { Subject } from 'rxjs'; |
||||
|
import { takeUntil } from 'rxjs/operators'; |
||||
|
import { FuseVerticalNavigationComponent } from '@fuse/components/navigation/vertical/vertical.component'; |
||||
|
import { FuseNavigationService } from '@fuse/components/navigation/navigation.service'; |
||||
|
import { FuseNavigationItem } from '@fuse/components/navigation/navigation.types'; |
||||
|
|
||||
|
@Component({ |
||||
|
selector : 'fuse-vertical-navigation-group-item', |
||||
|
templateUrl : './group.component.html', |
||||
|
changeDetection: ChangeDetectionStrategy.OnPush |
||||
|
}) |
||||
|
export class FuseVerticalNavigationGroupItemComponent implements OnInit, OnDestroy |
||||
|
{ |
||||
|
/* eslint-disable @typescript-eslint/naming-convention */ |
||||
|
static ngAcceptInputType_autoCollapse: BooleanInput; |
||||
|
/* eslint-enable @typescript-eslint/naming-convention */ |
||||
|
|
||||
|
@Input() autoCollapse: boolean; |
||||
|
@Input() item: FuseNavigationItem; |
||||
|
@Input() name: string; |
||||
|
|
||||
|
private _fuseVerticalNavigationComponent: FuseVerticalNavigationComponent; |
||||
|
private _unsubscribeAll: Subject<any> = new Subject<any>(); |
||||
|
|
||||
|
/** |
||||
|
* Constructor |
||||
|
*/ |
||||
|
constructor( |
||||
|
private _changeDetectorRef: ChangeDetectorRef, |
||||
|
private _fuseNavigationService: FuseNavigationService |
||||
|
) |
||||
|
{ |
||||
|
} |
||||
|
|
||||
|
// -----------------------------------------------------------------------------------------------------
|
||||
|
// @ Lifecycle hooks
|
||||
|
// -----------------------------------------------------------------------------------------------------
|
||||
|
|
||||
|
/** |
||||
|
* On init |
||||
|
*/ |
||||
|
ngOnInit(): void |
||||
|
{ |
||||
|
// Get the parent navigation component
|
||||
|
this._fuseVerticalNavigationComponent = this._fuseNavigationService.getComponent(this.name); |
||||
|
|
||||
|
// Subscribe to onRefreshed on the navigation component
|
||||
|
this._fuseVerticalNavigationComponent.onRefreshed.pipe( |
||||
|
takeUntil(this._unsubscribeAll) |
||||
|
).subscribe(() => { |
||||
|
|
||||
|
// Mark for check
|
||||
|
this._changeDetectorRef.markForCheck(); |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* On destroy |
||||
|
*/ |
||||
|
ngOnDestroy(): void |
||||
|
{ |
||||
|
// Unsubscribe from all subscriptions
|
||||
|
this._unsubscribeAll.next(); |
||||
|
this._unsubscribeAll.complete(); |
||||
|
} |
||||
|
|
||||
|
// -----------------------------------------------------------------------------------------------------
|
||||
|
// @ Public methods
|
||||
|
// -----------------------------------------------------------------------------------------------------
|
||||
|
|
||||
|
/** |
||||
|
* Track by function for ngFor loops |
||||
|
* |
||||
|
* @param index |
||||
|
* @param item |
||||
|
*/ |
||||
|
trackByFn(index: number, item: any): any |
||||
|
{ |
||||
|
return item.id || index; |
||||
|
} |
||||
|
} |
@ -0,0 +1,4 @@ |
|||||
|
<!-- Spacer --> |
||||
|
<div |
||||
|
class="fuse-vertical-navigation-item-wrapper" |
||||
|
[ngClass]="item.classes?.wrapper"></div> |
@ -0,0 +1,62 @@ |
|||||
|
import { ChangeDetectionStrategy, ChangeDetectorRef, Component, Input, OnDestroy, OnInit } from '@angular/core'; |
||||
|
import { takeUntil } from 'rxjs/operators'; |
||||
|
import { Subject } from 'rxjs'; |
||||
|
import { FuseVerticalNavigationComponent } from '@fuse/components/navigation/vertical/vertical.component'; |
||||
|
import { FuseNavigationService } from '@fuse/components/navigation/navigation.service'; |
||||
|
import { FuseNavigationItem } from '@fuse/components/navigation/navigation.types'; |
||||
|
|
||||
|
@Component({ |
||||
|
selector : 'fuse-vertical-navigation-spacer-item', |
||||
|
templateUrl : './spacer.component.html', |
||||
|
changeDetection: ChangeDetectionStrategy.OnPush |
||||
|
}) |
||||
|
export class FuseVerticalNavigationSpacerItemComponent implements OnInit, OnDestroy |
||||
|
{ |
||||
|
@Input() item: FuseNavigationItem; |
||||
|
@Input() name: string; |
||||
|
|
||||
|
private _fuseVerticalNavigationComponent: FuseVerticalNavigationComponent; |
||||
|
private _unsubscribeAll: Subject<any> = new Subject<any>(); |
||||
|
|
||||
|
/** |
||||
|
* Constructor |
||||
|
*/ |
||||
|
constructor( |
||||
|
private _changeDetectorRef: ChangeDetectorRef, |
||||
|
private _fuseNavigationService: FuseNavigationService |
||||
|
) |
||||
|
{ |
||||
|
} |
||||
|
|
||||
|
// -----------------------------------------------------------------------------------------------------
|
||||
|
// @ Lifecycle hooks
|
||||
|
// -----------------------------------------------------------------------------------------------------
|
||||
|
|
||||
|
/** |
||||
|
* On init |
||||
|
*/ |
||||
|
ngOnInit(): void |
||||
|
{ |
||||
|
// Get the parent navigation component
|
||||
|
this._fuseVerticalNavigationComponent = this._fuseNavigationService.getComponent(this.name); |
||||
|
|
||||
|
// Subscribe to onRefreshed on the navigation component
|
||||
|
this._fuseVerticalNavigationComponent.onRefreshed.pipe( |
||||
|
takeUntil(this._unsubscribeAll) |
||||
|
).subscribe(() => { |
||||
|
|
||||
|
// Mark for check
|
||||
|
this._changeDetectorRef.markForCheck(); |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* On destroy |
||||
|
*/ |
||||
|
ngOnDestroy(): void |
||||
|
{ |
||||
|
// Unsubscribe from all subscriptions
|
||||
|
this._unsubscribeAll.next(); |
||||
|
this._unsubscribeAll.complete(); |
||||
|
} |
||||
|
} |
@ -0,0 +1,110 @@ |
|||||
|
/* Variables */ |
||||
|
$fuse-vertical-navigation-compact-width: 112; |
||||
|
|
||||
|
fuse-vertical-navigation { |
||||
|
|
||||
|
/* Compact appearance overrides */ |
||||
|
&.fuse-vertical-navigation-appearance-compact { |
||||
|
width: #{$fuse-vertical-navigation-compact-width}px; |
||||
|
min-width: #{$fuse-vertical-navigation-compact-width}px; |
||||
|
max-width: #{$fuse-vertical-navigation-compact-width}px; |
||||
|
|
||||
|
/* Left positioned */ |
||||
|
&.fuse-vertical-navigation-position-left { |
||||
|
|
||||
|
/* Side mode */ |
||||
|
&.fuse-vertical-navigation-mode-side { |
||||
|
margin-left: -#{$fuse-vertical-navigation-compact-width}px; |
||||
|
} |
||||
|
|
||||
|
/* Opened */ |
||||
|
&.fuse-vertical-navigation-opened { |
||||
|
margin-left: 0; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/* Right positioned */ |
||||
|
&.fuse-vertical-navigation-position-right { |
||||
|
|
||||
|
/* Side mode */ |
||||
|
&.fuse-vertical-navigation-mode-side { |
||||
|
margin-right: -#{$fuse-vertical-navigation-compact-width}px; |
||||
|
} |
||||
|
|
||||
|
/* Opened */ |
||||
|
&.fuse-vertical-navigation-opened { |
||||
|
margin-right: 0; |
||||
|
} |
||||
|
|
||||
|
/* Aside wrapper */ |
||||
|
.fuse-vertical-navigation-aside-wrapper { |
||||
|
left: auto; |
||||
|
right: #{$fuse-vertical-navigation-compact-width}px; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/* Wrapper */ |
||||
|
.fuse-vertical-navigation-wrapper { |
||||
|
|
||||
|
/* Content */ |
||||
|
.fuse-vertical-navigation-content { |
||||
|
|
||||
|
> fuse-vertical-navigation-aside-item, |
||||
|
> fuse-vertical-navigation-basic-item { |
||||
|
|
||||
|
.fuse-vertical-navigation-item-wrapper { |
||||
|
margin: 4px 8px 0 8px; |
||||
|
|
||||
|
.fuse-vertical-navigation-item { |
||||
|
flex-direction: column; |
||||
|
justify-content: center; |
||||
|
padding: 12px; |
||||
|
border-radius: 6px; |
||||
|
|
||||
|
.fuse-vertical-navigation-item-icon { |
||||
|
margin-right: 0; |
||||
|
} |
||||
|
|
||||
|
.fuse-vertical-navigation-item-title-wrapper { |
||||
|
margin-top: 8px; |
||||
|
|
||||
|
.fuse-vertical-navigation-item-title { |
||||
|
font-size: 12px; |
||||
|
font-weight: 500; |
||||
|
text-align: center; |
||||
|
line-height: 16px; |
||||
|
} |
||||
|
|
||||
|
.fuse-vertical-navigation-item-subtitle { |
||||
|
display: none !important; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
.fuse-vertical-navigation-item-badge { |
||||
|
position: absolute; |
||||
|
top: 12px; |
||||
|
left: 64px; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
> fuse-vertical-navigation-collapsable-item { |
||||
|
display: none |
||||
|
} |
||||
|
|
||||
|
> fuse-vertical-navigation-group-item { |
||||
|
|
||||
|
> .fuse-vertical-navigation-item-wrapper { |
||||
|
display: none |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/* Aside wrapper */ |
||||
|
.fuse-vertical-navigation-aside-wrapper { |
||||
|
left: #{$fuse-vertical-navigation-compact-width}px; |
||||
|
} |
||||
|
} |
||||
|
} |
@ -0,0 +1,592 @@ |
|||||
|
/* Variables */ |
||||
|
$fuse-vertical-navigation-width: 280; |
||||
|
|
||||
|
fuse-vertical-navigation { |
||||
|
position: sticky; |
||||
|
display: flex; |
||||
|
flex-direction: column; |
||||
|
flex: 1 0 auto; |
||||
|
top: 0; |
||||
|
width: #{$fuse-vertical-navigation-width}px; |
||||
|
min-width: #{$fuse-vertical-navigation-width}px; |
||||
|
max-width: #{$fuse-vertical-navigation-width}px; |
||||
|
height: 100vh; |
||||
|
min-height: 100vh; |
||||
|
max-height: 100vh; |
||||
|
z-index: 200; |
||||
|
|
||||
|
/* ----------------------------------------------------------------------------------------------------- */ |
||||
|
/* @ Navigation Drawer |
||||
|
/* ----------------------------------------------------------------------------------------------------- */ |
||||
|
|
||||
|
/* Animations */ |
||||
|
&.fuse-vertical-navigation-animations-enabled { |
||||
|
transition-duration: 400ms; |
||||
|
transition-timing-function: cubic-bezier(0.25, 0.8, 0.25, 1); |
||||
|
transition-property: visibility, margin-left, margin-right, transform, width, max-width, min-width; |
||||
|
|
||||
|
/* Wrapper */ |
||||
|
.fuse-vertical-navigation-wrapper { |
||||
|
transition-duration: 400ms; |
||||
|
transition-timing-function: cubic-bezier(0.25, 0.8, 0.25, 1); |
||||
|
transition-property: width, max-width, min-width; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/* Over mode */ |
||||
|
&.fuse-vertical-navigation-mode-over { |
||||
|
position: fixed; |
||||
|
top: 0; |
||||
|
bottom: 0; |
||||
|
} |
||||
|
|
||||
|
/* Left position */ |
||||
|
&.fuse-vertical-navigation-position-left { |
||||
|
|
||||
|
/* Side mode */ |
||||
|
&.fuse-vertical-navigation-mode-side { |
||||
|
margin-left: -#{$fuse-vertical-navigation-width}px; |
||||
|
|
||||
|
&.fuse-vertical-navigation-opened { |
||||
|
margin-left: 0; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/* Over mode */ |
||||
|
&.fuse-vertical-navigation-mode-over { |
||||
|
left: 0; |
||||
|
transform: translate3d(-100%, 0, 0); |
||||
|
|
||||
|
&.fuse-vertical-navigation-opened { |
||||
|
transform: translate3d(0, 0, 0); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/* Wrapper */ |
||||
|
.fuse-vertical-navigation-wrapper { |
||||
|
left: 0; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/* Right position */ |
||||
|
&.fuse-vertical-navigation-position-right { |
||||
|
|
||||
|
/* Side mode */ |
||||
|
&.fuse-vertical-navigation-mode-side { |
||||
|
margin-right: -#{$fuse-vertical-navigation-width}px; |
||||
|
|
||||
|
&.fuse-vertical-navigation-opened { |
||||
|
margin-right: 0; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/* Over mode */ |
||||
|
&.fuse-vertical-navigation-mode-over { |
||||
|
right: 0; |
||||
|
transform: translate3d(100%, 0, 0); |
||||
|
|
||||
|
&.fuse-vertical-navigation-opened { |
||||
|
transform: translate3d(0, 0, 0); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/* Wrapper */ |
||||
|
.fuse-vertical-navigation-wrapper { |
||||
|
right: 0; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/* Inner mode */ |
||||
|
&.fuse-vertical-navigation-inner { |
||||
|
position: relative; |
||||
|
width: auto; |
||||
|
min-width: 0; |
||||
|
max-width: none; |
||||
|
height: auto; |
||||
|
min-height: 0; |
||||
|
max-height: none; |
||||
|
box-shadow: none; |
||||
|
|
||||
|
.fuse-vertical-navigation-wrapper { |
||||
|
position: relative; |
||||
|
overflow: visible; |
||||
|
height: auto; |
||||
|
|
||||
|
.fuse-vertical-navigation-content { |
||||
|
overflow: visible !important; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/* Wrapper */ |
||||
|
.fuse-vertical-navigation-wrapper { |
||||
|
position: absolute; |
||||
|
display: flex; |
||||
|
flex: 1 1 auto; |
||||
|
flex-direction: column; |
||||
|
top: 0; |
||||
|
bottom: 0; |
||||
|
width: 100%; |
||||
|
height: 100%; |
||||
|
overflow: hidden; |
||||
|
z-index: 10; |
||||
|
background: inherit; |
||||
|
box-shadow: inset -1px 0 0 var(--fuse-border); |
||||
|
|
||||
|
/* Header */ |
||||
|
.fuse-vertical-navigation-header { |
||||
|
|
||||
|
} |
||||
|
|
||||
|
/* Content */ |
||||
|
.fuse-vertical-navigation-content { |
||||
|
flex: 1 1 auto; |
||||
|
overflow-x: hidden; |
||||
|
overflow-y: auto; |
||||
|
overscroll-behavior: contain; |
||||
|
|
||||
|
/* Divider */ |
||||
|
> fuse-vertical-navigation-divider-item { |
||||
|
margin: 24px 0; |
||||
|
} |
||||
|
|
||||
|
/* Group */ |
||||
|
> fuse-vertical-navigation-group-item { |
||||
|
margin-top: 24px; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/* Footer */ |
||||
|
.fuse-vertical-navigation-footer { |
||||
|
|
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/* Aside wrapper */ |
||||
|
.fuse-vertical-navigation-aside-wrapper { |
||||
|
position: absolute; |
||||
|
display: flex; |
||||
|
flex: 1 1 auto; |
||||
|
flex-direction: column; |
||||
|
top: 0; |
||||
|
bottom: 0; |
||||
|
left: #{$fuse-vertical-navigation-width}px; |
||||
|
width: #{$fuse-vertical-navigation-width}px; |
||||
|
height: 100%; |
||||
|
z-index: 5; |
||||
|
overflow-x: hidden; |
||||
|
overflow-y: auto; |
||||
|
-webkit-overflow-scrolling: touch; |
||||
|
transition-duration: 400ms; |
||||
|
transition-property: left, right; |
||||
|
transition-timing-function: cubic-bezier(0.25, 0.8, 0.25, 1); |
||||
|
background: inherit; |
||||
|
|
||||
|
> fuse-vertical-navigation-aside-item { |
||||
|
padding: 24px 0; |
||||
|
|
||||
|
/* First item of the aside */ |
||||
|
> .fuse-vertical-navigation-item-wrapper { |
||||
|
display: none !important; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
&.fuse-vertical-navigation-position-right { |
||||
|
|
||||
|
.fuse-vertical-navigation-aside-wrapper { |
||||
|
left: auto; |
||||
|
right: #{$fuse-vertical-navigation-width}px; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/* ----------------------------------------------------------------------------------------------------- */ |
||||
|
/* @ Navigation Items |
||||
|
/* ----------------------------------------------------------------------------------------------------- */ |
||||
|
|
||||
|
/* Navigation items common */ |
||||
|
fuse-vertical-navigation-aside-item, |
||||
|
fuse-vertical-navigation-basic-item, |
||||
|
fuse-vertical-navigation-collapsable-item, |
||||
|
fuse-vertical-navigation-divider-item, |
||||
|
fuse-vertical-navigation-group-item, |
||||
|
fuse-vertical-navigation-spacer-item { |
||||
|
display: flex; |
||||
|
flex-direction: column; |
||||
|
flex: 1 0 auto; |
||||
|
user-select: none; |
||||
|
|
||||
|
.fuse-vertical-navigation-item-wrapper { |
||||
|
|
||||
|
.fuse-vertical-navigation-item { |
||||
|
position: relative; |
||||
|
display: flex; |
||||
|
align-items: center; |
||||
|
justify-content: flex-start; |
||||
|
padding: 10px 16px; |
||||
|
font-size: 13px; |
||||
|
font-weight: 500; |
||||
|
line-height: 20px; |
||||
|
text-decoration: none; |
||||
|
border-radius: 6px; |
||||
|
|
||||
|
/* Disabled state */ |
||||
|
&.fuse-vertical-navigation-item-disabled { |
||||
|
cursor: default; |
||||
|
opacity: 0.4; |
||||
|
} |
||||
|
|
||||
|
.fuse-vertical-navigation-item-icon { |
||||
|
margin-right: 16px; |
||||
|
} |
||||
|
|
||||
|
.fuse-vertical-navigation-item-title-wrapper { |
||||
|
|
||||
|
.fuse-vertical-navigation-item-subtitle { |
||||
|
font-size: 11px; |
||||
|
line-height: 1.5; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
.fuse-vertical-navigation-item-badge { |
||||
|
margin-left: auto; |
||||
|
|
||||
|
.fuse-vertical-navigation-item-badge-content { |
||||
|
display: flex; |
||||
|
align-items: center; |
||||
|
justify-content: center; |
||||
|
font-size: 10px; |
||||
|
font-weight: 600; |
||||
|
white-space: nowrap; |
||||
|
height: 20px; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/* Aside, Basic, Collapsable, Group */ |
||||
|
fuse-vertical-navigation-aside-item, |
||||
|
fuse-vertical-navigation-basic-item, |
||||
|
fuse-vertical-navigation-collapsable-item, |
||||
|
fuse-vertical-navigation-group-item { |
||||
|
|
||||
|
> .fuse-vertical-navigation-item-wrapper { |
||||
|
margin: 0 12px; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/* Aside, Basic, Collapsable */ |
||||
|
fuse-vertical-navigation-aside-item, |
||||
|
fuse-vertical-navigation-basic-item, |
||||
|
fuse-vertical-navigation-collapsable-item { |
||||
|
margin-bottom: 4px; |
||||
|
|
||||
|
.fuse-vertical-navigation-item { |
||||
|
cursor: pointer; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/* Aside */ |
||||
|
fuse-vertical-navigation-aside-item { |
||||
|
|
||||
|
} |
||||
|
|
||||
|
/* Basic */ |
||||
|
fuse-vertical-navigation-basic-item { |
||||
|
|
||||
|
} |
||||
|
|
||||
|
/* Collapsable */ |
||||
|
fuse-vertical-navigation-collapsable-item { |
||||
|
|
||||
|
> .fuse-vertical-navigation-item-wrapper { |
||||
|
|
||||
|
.fuse-vertical-navigation-item { |
||||
|
|
||||
|
.fuse-vertical-navigation-item-badge { |
||||
|
|
||||
|
+ .fuse-vertical-navigation-item-arrow { |
||||
|
margin-left: 8px; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
.fuse-vertical-navigation-item-arrow { |
||||
|
height: 20px; |
||||
|
line-height: 20px; |
||||
|
margin-left: auto; |
||||
|
transition: transform 300ms cubic-bezier(0.25, 0.8, 0.25, 1), |
||||
|
color 375ms cubic-bezier(0.25, 0.8, 0.25, 1); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
&.fuse-vertical-navigation-item-expanded { |
||||
|
|
||||
|
> .fuse-vertical-navigation-item-wrapper { |
||||
|
|
||||
|
.fuse-vertical-navigation-item { |
||||
|
|
||||
|
.fuse-vertical-navigation-item-arrow { |
||||
|
transform: rotate(90deg); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
> .fuse-vertical-navigation-item-children { |
||||
|
margin-top: 6px; |
||||
|
|
||||
|
> *:last-child { |
||||
|
padding-bottom: 6px; |
||||
|
|
||||
|
> .fuse-vertical-navigation-item-children { |
||||
|
|
||||
|
> *:last-child { |
||||
|
padding-bottom: 0; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
.fuse-vertical-navigation-item { |
||||
|
padding: 10px 16px; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/* 1st level */ |
||||
|
.fuse-vertical-navigation-item-children { |
||||
|
overflow: hidden; |
||||
|
|
||||
|
.fuse-vertical-navigation-item { |
||||
|
padding-left: 56px; |
||||
|
} |
||||
|
|
||||
|
/* 2nd level */ |
||||
|
.fuse-vertical-navigation-item-children { |
||||
|
|
||||
|
.fuse-vertical-navigation-item { |
||||
|
padding-left: 72px; |
||||
|
} |
||||
|
|
||||
|
/* 3rd level */ |
||||
|
.fuse-vertical-navigation-item-children { |
||||
|
|
||||
|
.fuse-vertical-navigation-item { |
||||
|
padding-left: 88px; |
||||
|
} |
||||
|
|
||||
|
/* 4th level */ |
||||
|
.fuse-vertical-navigation-item-children { |
||||
|
|
||||
|
.fuse-vertical-navigation-item { |
||||
|
padding-left: 104px; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/* Divider */ |
||||
|
fuse-vertical-navigation-divider-item { |
||||
|
margin: 12px 0; |
||||
|
|
||||
|
.fuse-vertical-navigation-item-wrapper { |
||||
|
height: 1px; |
||||
|
box-shadow: 0 1px 0 0; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/* Group */ |
||||
|
fuse-vertical-navigation-group-item { |
||||
|
|
||||
|
> .fuse-vertical-navigation-item-wrapper { |
||||
|
|
||||
|
.fuse-vertical-navigation-item { |
||||
|
|
||||
|
.fuse-vertical-navigation-item-badge, |
||||
|
.fuse-vertical-navigation-item-icon { |
||||
|
display: none !important; |
||||
|
} |
||||
|
|
||||
|
.fuse-vertical-navigation-item-title-wrapper { |
||||
|
|
||||
|
.fuse-vertical-navigation-item-title { |
||||
|
font-size: 12px; |
||||
|
font-weight: 600; |
||||
|
letter-spacing: 0.05em; |
||||
|
text-transform: uppercase; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/* Spacer */ |
||||
|
fuse-vertical-navigation-spacer-item { |
||||
|
margin: 6px 0; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/* ----------------------------------------------------------------------------------------------------- */ |
||||
|
/* @ Overlay |
||||
|
/* ----------------------------------------------------------------------------------------------------- */ |
||||
|
.fuse-vertical-navigation-overlay { |
||||
|
position: absolute; |
||||
|
top: 0; |
||||
|
bottom: 0; |
||||
|
left: 0; |
||||
|
right: 0; |
||||
|
z-index: 170; |
||||
|
opacity: 0; |
||||
|
background-color: rgba(0, 0, 0, 0.6); |
||||
|
|
||||
|
+ .fuse-vertical-navigation-aside-overlay { |
||||
|
background-color: transparent; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/* ----------------------------------------------------------------------------------------------------- */ |
||||
|
/* @ Aside overlay |
||||
|
/* ----------------------------------------------------------------------------------------------------- */ |
||||
|
.fuse-vertical-navigation-aside-overlay { |
||||
|
position: absolute; |
||||
|
top: 0; |
||||
|
bottom: 0; |
||||
|
left: 0; |
||||
|
right: 0; |
||||
|
z-index: 169; |
||||
|
opacity: 0; |
||||
|
background-color: rgba(0, 0, 0, 0.3); |
||||
|
} |
||||
|
|
||||
|
/* ----------------------------------------------------------------------------------------------------- */ |
||||
|
/* @ Navigation Items Colors |
||||
|
/* ----------------------------------------------------------------------------------------------------- */ |
||||
|
|
||||
|
/* Navigation items common */ |
||||
|
fuse-vertical-navigation-aside-item, |
||||
|
fuse-vertical-navigation-basic-item, |
||||
|
fuse-vertical-navigation-collapsable-item, |
||||
|
fuse-vertical-navigation-group-item { |
||||
|
|
||||
|
.fuse-vertical-navigation-item-wrapper { |
||||
|
|
||||
|
.fuse-vertical-navigation-item { |
||||
|
color: currentColor; |
||||
|
|
||||
|
.fuse-vertical-navigation-item-icon { |
||||
|
@apply text-current opacity-60; |
||||
|
} |
||||
|
|
||||
|
.fuse-vertical-navigation-item-title-wrapper { |
||||
|
|
||||
|
.fuse-vertical-navigation-item-title { |
||||
|
@apply text-current opacity-80; |
||||
|
} |
||||
|
|
||||
|
.fuse-vertical-navigation-item-subtitle { |
||||
|
@apply text-current opacity-50; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/* Aside, Basic, Collapsable */ |
||||
|
fuse-vertical-navigation-aside-item, |
||||
|
fuse-vertical-navigation-basic-item, |
||||
|
fuse-vertical-navigation-collapsable-item { |
||||
|
|
||||
|
> .fuse-vertical-navigation-item-wrapper { |
||||
|
|
||||
|
.fuse-vertical-navigation-item { |
||||
|
|
||||
|
/* Active state */ |
||||
|
&:not(.fuse-vertical-navigation-item-disabled) { |
||||
|
|
||||
|
&.fuse-vertical-navigation-item-active, |
||||
|
&.fuse-vertical-navigation-item-active-forced { |
||||
|
@apply bg-gray-800 bg-opacity-5 dark:bg-white dark:bg-opacity-12; |
||||
|
|
||||
|
.fuse-vertical-navigation-item-icon { |
||||
|
@apply opacity-100; |
||||
|
} |
||||
|
|
||||
|
.fuse-vertical-navigation-item-title { |
||||
|
@apply opacity-100; |
||||
|
} |
||||
|
|
||||
|
.fuse-vertical-navigation-item-subtitle { |
||||
|
@apply opacity-100; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/* Hover state */ |
||||
|
&:not(.fuse-vertical-navigation-item-active-forced):not(.fuse-vertical-navigation-item-active):not(.fuse-vertical-navigation-item-disabled) { |
||||
|
|
||||
|
&:hover { |
||||
|
@apply bg-gray-800 bg-opacity-5 dark:bg-white dark:bg-opacity-12; |
||||
|
|
||||
|
.fuse-vertical-navigation-item-icon { |
||||
|
@apply opacity-100; |
||||
|
} |
||||
|
|
||||
|
.fuse-vertical-navigation-item-title, |
||||
|
.fuse-vertical-navigation-item-arrow { |
||||
|
@apply opacity-100; |
||||
|
} |
||||
|
|
||||
|
.fuse-vertical-navigation-item-subtitle { |
||||
|
@apply opacity-100; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/* Collapsable */ |
||||
|
fuse-vertical-navigation-collapsable-item { |
||||
|
|
||||
|
/* Expanded state */ |
||||
|
&.fuse-vertical-navigation-item-expanded { |
||||
|
|
||||
|
> .fuse-vertical-navigation-item-wrapper { |
||||
|
|
||||
|
.fuse-vertical-navigation-item { |
||||
|
|
||||
|
.fuse-vertical-navigation-item-icon { |
||||
|
@apply opacity-100; |
||||
|
} |
||||
|
|
||||
|
.fuse-vertical-navigation-item-title, |
||||
|
.fuse-vertical-navigation-item-arrow { |
||||
|
@apply opacity-100; |
||||
|
} |
||||
|
|
||||
|
.fuse-vertical-navigation-item-subtitle { |
||||
|
@apply opacity-100; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/* Group */ |
||||
|
fuse-vertical-navigation-group-item { |
||||
|
|
||||
|
> .fuse-vertical-navigation-item-wrapper { |
||||
|
|
||||
|
.fuse-vertical-navigation-item { |
||||
|
|
||||
|
.fuse-vertical-navigation-item-title-wrapper { |
||||
|
|
||||
|
.fuse-vertical-navigation-item-title { |
||||
|
@apply opacity-100 text-primary-600 dark:text-primary-400; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
@ -0,0 +1,189 @@ |
|||||
|
/* Variables */ |
||||
|
$fuse-vertical-navigation-width: 280; |
||||
|
$fuse-vertical-navigation-dense-width: 80; |
||||
|
|
||||
|
fuse-vertical-navigation { |
||||
|
|
||||
|
/* Dense appearance overrides */ |
||||
|
&.fuse-vertical-navigation-appearance-dense { |
||||
|
|
||||
|
&:not(.fuse-vertical-navigation-mode-over) { |
||||
|
width: #{$fuse-vertical-navigation-dense-width}px; |
||||
|
min-width: #{$fuse-vertical-navigation-dense-width}px; |
||||
|
max-width: #{$fuse-vertical-navigation-dense-width}px; |
||||
|
|
||||
|
/* Left positioned */ |
||||
|
&.fuse-vertical-navigation-position-left { |
||||
|
|
||||
|
/* Side mode */ |
||||
|
&.fuse-vertical-navigation-mode-side { |
||||
|
margin-left: -#{$fuse-vertical-navigation-dense-width}px; |
||||
|
} |
||||
|
|
||||
|
/* Opened */ |
||||
|
&.fuse-vertical-navigation-opened { |
||||
|
margin-left: 0; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/* Right positioned */ |
||||
|
&.fuse-vertical-navigation-position-right { |
||||
|
|
||||
|
/* Side mode */ |
||||
|
&.fuse-vertical-navigation-mode-side { |
||||
|
margin-right: -#{$fuse-vertical-navigation-dense-width}px; |
||||
|
} |
||||
|
|
||||
|
/* Opened */ |
||||
|
&.fuse-vertical-navigation-opened { |
||||
|
margin-right: 0; |
||||
|
} |
||||
|
|
||||
|
/* Aside wrapper */ |
||||
|
.fuse-vertical-navigation-aside-wrapper { |
||||
|
left: auto; |
||||
|
right: #{$fuse-vertical-navigation-dense-width}px; |
||||
|
} |
||||
|
|
||||
|
&.fuse-vertical-navigation-hover { |
||||
|
|
||||
|
.fuse-vertical-navigation-aside-wrapper { |
||||
|
left: auto; |
||||
|
right: #{$fuse-vertical-navigation-width}px; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/* Wrapper */ |
||||
|
.fuse-vertical-navigation-wrapper { |
||||
|
|
||||
|
/* Content */ |
||||
|
.fuse-vertical-navigation-content { |
||||
|
|
||||
|
fuse-vertical-navigation-aside-item, |
||||
|
fuse-vertical-navigation-basic-item, |
||||
|
fuse-vertical-navigation-collapsable-item, |
||||
|
fuse-vertical-navigation-group-item { |
||||
|
|
||||
|
.fuse-vertical-navigation-item-wrapper { |
||||
|
|
||||
|
.fuse-vertical-navigation-item { |
||||
|
width: #{$fuse-vertical-navigation-width}px - 24px; |
||||
|
min-width: #{$fuse-vertical-navigation-width}px - 24px; |
||||
|
max-width: #{$fuse-vertical-navigation-width}px - 24px; |
||||
|
|
||||
|
.fuse-vertical-navigation-item-arrow, |
||||
|
.fuse-vertical-navigation-item-badge, |
||||
|
.fuse-vertical-navigation-item-title-wrapper { |
||||
|
transition: opacity 400ms cubic-bezier(0.25, 0.8, 0.25, 1); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
fuse-vertical-navigation-group-item { |
||||
|
|
||||
|
&:first-of-type { |
||||
|
margin-top: 0; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
&:not(.fuse-vertical-navigation-hover):not(.fuse-vertical-navigation-mode-over) { |
||||
|
|
||||
|
/* Wrapper */ |
||||
|
.fuse-vertical-navigation-wrapper { |
||||
|
|
||||
|
/* Content */ |
||||
|
.fuse-vertical-navigation-content { |
||||
|
|
||||
|
.fuse-vertical-navigation-item-wrapper { |
||||
|
|
||||
|
.fuse-vertical-navigation-item { |
||||
|
padding: 10px 16px; |
||||
|
|
||||
|
.fuse-vertical-navigation-item-arrow, |
||||
|
.fuse-vertical-navigation-item-badge, |
||||
|
.fuse-vertical-navigation-item-title-wrapper { |
||||
|
white-space: nowrap; |
||||
|
opacity: 0; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
fuse-vertical-navigation-collapsable-item { |
||||
|
|
||||
|
.fuse-vertical-navigation-item-children { |
||||
|
display: none; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
fuse-vertical-navigation-group-item { |
||||
|
|
||||
|
> .fuse-vertical-navigation-item-wrapper { |
||||
|
|
||||
|
.fuse-vertical-navigation-item { |
||||
|
|
||||
|
&:before { |
||||
|
content: ''; |
||||
|
position: absolute; |
||||
|
top: 20px; |
||||
|
width: 23px; |
||||
|
border-top-width: 2px; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/* Aside wrapper */ |
||||
|
.fuse-vertical-navigation-aside-wrapper { |
||||
|
left: #{$fuse-vertical-navigation-dense-width}px; |
||||
|
} |
||||
|
|
||||
|
/* Hover */ |
||||
|
&.fuse-vertical-navigation-hover { |
||||
|
|
||||
|
.fuse-vertical-navigation-wrapper { |
||||
|
width: #{$fuse-vertical-navigation-width}px; |
||||
|
|
||||
|
.fuse-vertical-navigation-content { |
||||
|
|
||||
|
.fuse-vertical-navigation-item-wrapper { |
||||
|
|
||||
|
.fuse-vertical-navigation-item { |
||||
|
|
||||
|
.fuse-vertical-navigation-item-arrow, |
||||
|
.fuse-vertical-navigation-item-badge, |
||||
|
.fuse-vertical-navigation-item-title-wrapper { |
||||
|
white-space: nowrap; |
||||
|
animation: removeWhiteSpaceNoWrap 1ms linear 350ms; |
||||
|
animation-fill-mode: forwards; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
.fuse-vertical-navigation-aside-wrapper { |
||||
|
left: #{$fuse-vertical-navigation-width}px; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
@keyframes removeWhiteSpaceNoWrap { |
||||
|
0% { |
||||
|
white-space: nowrap |
||||
|
} |
||||
|
99% { |
||||
|
white-space: nowrap |
||||
|
} |
||||
|
100% { |
||||
|
white-space: normal; |
||||
|
} |
||||
|
} |
@ -0,0 +1,97 @@ |
|||||
|
/* Variables */ |
||||
|
$fuse-vertical-navigation-thin-width: 80; |
||||
|
|
||||
|
fuse-vertical-navigation { |
||||
|
|
||||
|
/* Thin appearance overrides */ |
||||
|
&.fuse-vertical-navigation-appearance-thin { |
||||
|
width: #{$fuse-vertical-navigation-thin-width}px; |
||||
|
min-width: #{$fuse-vertical-navigation-thin-width}px; |
||||
|
max-width: #{$fuse-vertical-navigation-thin-width}px; |
||||
|
|
||||
|
/* Left positioned */ |
||||
|
&.fuse-vertical-navigation-position-left { |
||||
|
|
||||
|
&.fuse-vertical-navigation-mode-side { |
||||
|
margin-left: -#{$fuse-vertical-navigation-thin-width}px; |
||||
|
} |
||||
|
|
||||
|
&.fuse-vertical-navigation-opened { |
||||
|
margin-left: 0; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/* Right positioned */ |
||||
|
&.fuse-vertical-navigation-position-right { |
||||
|
|
||||
|
&.fuse-vertical-navigation-mode-side { |
||||
|
margin-right: -#{$fuse-vertical-navigation-thin-width}px; |
||||
|
} |
||||
|
|
||||
|
&.fuse-vertical-navigation-opened { |
||||
|
margin-right: 0; |
||||
|
} |
||||
|
|
||||
|
.fuse-vertical-navigation-aside-wrapper { |
||||
|
left: auto; |
||||
|
right: #{$fuse-vertical-navigation-thin-width}px; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/* Wrapper */ |
||||
|
.fuse-vertical-navigation-wrapper { |
||||
|
|
||||
|
/* Content */ |
||||
|
.fuse-vertical-navigation-content { |
||||
|
|
||||
|
> fuse-vertical-navigation-aside-item, |
||||
|
> fuse-vertical-navigation-basic-item { |
||||
|
flex-direction: column; |
||||
|
justify-content: center; |
||||
|
height: 64px; |
||||
|
min-height: 64px; |
||||
|
max-height: 64px; |
||||
|
padding: 0 16px; |
||||
|
|
||||
|
.fuse-vertical-navigation-item-wrapper { |
||||
|
display: flex; |
||||
|
align-items: center; |
||||
|
justify-content: center; |
||||
|
|
||||
|
.fuse-vertical-navigation-item { |
||||
|
justify-content: center; |
||||
|
padding: 12px; |
||||
|
border-radius: 4px; |
||||
|
|
||||
|
.fuse-vertical-navigation-item-icon { |
||||
|
margin: 0; |
||||
|
} |
||||
|
|
||||
|
.fuse-vertical-navigation-item-arrow, |
||||
|
.fuse-vertical-navigation-item-badge-content, |
||||
|
.fuse-vertical-navigation-item-title-wrapper { |
||||
|
display: none; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
> fuse-vertical-navigation-collapsable-item { |
||||
|
display: none |
||||
|
} |
||||
|
|
||||
|
> fuse-vertical-navigation-group-item { |
||||
|
|
||||
|
> .fuse-vertical-navigation-item-wrapper { |
||||
|
display: none |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/* Aside wrapper */ |
||||
|
.fuse-vertical-navigation-aside-wrapper { |
||||
|
left: #{$fuse-vertical-navigation-thin-width}px; |
||||
|
} |
||||
|
} |
||||
|
} |
Some files were not shown because too many files changed in this diff
Loading…
Reference in new issue