From 1994c98063c27a41797dec01f2ca9fcbe33ceab0 Mon Sep 17 00:00:00 2001 From: Israel Lavi Date: Mon, 21 May 2018 17:42:00 +0300 Subject: init commit onap ui Change-Id: I1dace78817dbba752c550c182dfea118b4a38646 Issue-ID: SDC-1350 Signed-off-by: Israel Lavi --- src/README.md | 9 + src/angular/accordion/accordion.component.html.ts | 21 + src/angular/accordion/accordion.component.ts | 27 + src/angular/accordion/accordion.module.ts | 23 + .../animations/animation-directives.module.ts | 19 + .../animations/ripple-click.animation.directive.ts | 47 ++ .../autocomplete/autocomplete.component.html.ts | 14 + src/angular/autocomplete/autocomplete.component.ts | 114 ++++ src/angular/autocomplete/autocomplete.module.ts | 23 + src/angular/autocomplete/autocomplete.pipe.ts | 16 + src/angular/buttons/button.component.html.ts | 15 + src/angular/buttons/button.component.ts | 62 +++ src/angular/buttons/buttons.module.ts | 21 + src/angular/checklist/checklist.component.html.ts | 15 + src/angular/checklist/checklist.component.ts | 50 ++ src/angular/checklist/checklist.module.ts | 11 + src/angular/checklist/models/Checklist.ts | 18 + src/angular/checklist/models/ChecklistItem.ts | 17 + src/angular/common/enums.ts | 34 ++ src/angular/common/index.ts | 3 + src/angular/components.ts | 50 ++ src/angular/filterbar/filter-bar.component.html.ts | 30 ++ src/angular/filterbar/filter-bar.component.ts | 30 ++ src/angular/filterbar/filter-bar.module.ts | 17 + .../checkbox/checkbox.component.html.ts | 8 + .../checkbox/checkbox.component.spec.ts | 37 ++ .../form-elements/checkbox/checkbox.component.ts | 21 + .../form-elements/dropdown/dropdown-models.ts | 18 + .../dropdown/dropdown-trigger.directive.ts | 17 + .../dropdown/dropdown.component.html.ts | 59 +++ .../dropdown/dropdown.component.spec.ts | 71 +++ .../form-elements/dropdown/dropdown.component.ts | 149 ++++++ src/angular/form-elements/form-elements.module.ts | 38 ++ .../form-elements/input/input.component.html.ts | 19 + src/angular/form-elements/input/input.component.ts | 54 ++ .../form-elements/radios/radio-button.model.ts | 15 + .../radios/radio-buttons-group.component.html.ts | 20 + .../radios/radio-buttons-group.component.spec.ts | 52 ++ .../radios/radio-buttons-group.component.ts | 52 ++ .../validation/validatable.component.ts | 25 + .../validation/validatable.interface.ts | 5 + .../validation/validation-group.component.html.ts | 3 + .../validation/validation-group.component.ts | 47 ++ .../validation/validation.component.html.ts | 3 + .../validation/validation.component.ts | 79 +++ .../form-elements/validation/validation.module.ts | 35 ++ .../validators/base.validator.component.html.ts | 10 + .../validators/base.validator.component.ts | 25 + .../validators/custom.validator.component.ts | 23 + .../validators/regex.validator.component.ts | 24 + .../validators/required.validator.component.ts | 25 + .../validation/validators/validator.interface.ts | 3 + src/angular/index.ts | 68 +++ .../infinite-scroll/infinite-scroll.directive.ts | 35 ++ .../infinite-scroll/infinite-scroll.module.ts | 13 + src/angular/modals/modal-button.component.ts | 29 ++ src/angular/modals/modal-close-button.component.ts | 34 ++ src/angular/modals/modal.component.html.ts | 38 ++ src/angular/modals/modal.component.spec.ts | 105 ++++ src/angular/modals/modal.component.ts | 96 ++++ src/angular/modals/modal.module.ts | 33 ++ src/angular/modals/modal.service.ts | 100 ++++ src/angular/modals/models/modal-config.ts | 44 ++ src/angular/ng1.module.ts | 135 +++++ .../container/notifcontainer.component.html.ts | 6 + .../container/notifcontainer.component.ts | 31 ++ ...notification-inner-content-example.component.ts | 21 + src/angular/notifications/notification.module.ts | 29 ++ .../notification/notification.component.html.ts | 19 + .../notification/notification.component.ts | 42 ++ .../services/notifications.service.ts | 41 ++ .../notifications/utilities/notification.config.ts | 30 ++ .../popup-menu/popup-menu-item.component.spec.ts | 25 + .../popup-menu/popup-menu-item.component.ts | 34 ++ .../popup-menu/popup-menu-list.component.ts | 65 +++ src/angular/popup-menu/popup-menu.module.ts | 21 + src/angular/searchbar/search-bar.component.html.ts | 19 + src/angular/searchbar/search-bar.component.ts | 19 + src/angular/searchbar/search-bar.module.ts | 17 + .../svg-icon/svg-icon-label.component.html.ts | 6 + src/angular/svg-icon/svg-icon-label.component.ts | 26 + src/angular/svg-icon/svg-icon.component.html.ts | 3 + src/angular/svg-icon/svg-icon.component.ts | 77 +++ src/angular/svg-icon/svg-icon.module.ts | 21 + src/angular/tabs/children/tab.component.html.ts | 5 + src/angular/tabs/children/tab.component.ts | 16 + src/angular/tabs/tabs.component.html.ts | 14 + src/angular/tabs/tabs.component.ts | 41 ++ src/angular/tabs/tabs.module.ts | 23 + src/angular/tag-cloud/tag-cloud.component.html.ts | 30 ++ src/angular/tag-cloud/tag-cloud.component.ts | 46 ++ src/angular/tag-cloud/tag-cloud.module.ts | 21 + .../tag-cloud/tag-item/tag-item.component.html.ts | 16 + .../tag-cloud/tag-item/tag-item.component.ts | 15 + .../tiles/children/tile-content.component.ts | 10 + .../tiles/children/tile-footer.component.ts | 10 + .../tiles/children/tile-header.component.ts | 10 + src/angular/tiles/tile.component.html.ts | 5 + src/angular/tiles/tile.component.ts | 11 + src/angular/tiles/tile.module.ts | 27 + src/angular/tooltip/tooltip-template.component.ts | 20 + src/angular/tooltip/tooltip.directive.ts | 459 ++++++++++++++++ src/angular/tooltip/tooltip.module.ts | 17 + .../utils/create-dynamic-component.service.ts | 101 ++++ src/react/Accordion.js | 40 ++ src/react/Button.js | 37 ++ src/react/Checkbox.js | 45 ++ src/react/Checklist.js | 43 ++ src/react/Input.js | 88 ++++ src/react/Modal.js | 55 ++ src/react/ModalBody.js | 19 + src/react/ModalFooter.js | 36 ++ src/react/ModalHeader.js | 41 ++ src/react/ModalTitle.js | 19 + src/react/Panel.js | 18 + src/react/PopupMenu.js | 39 ++ src/react/PopupMenuItem.js | 34 ++ src/react/Portal.js | 52 ++ src/react/Radio.js | 58 +++ src/react/RadioGroup.js | 40 ++ src/react/SVGIcon.js | 47 ++ src/react/Tab.js | 20 + src/react/TabPane.js | 12 + src/react/Tabs.js | 29 ++ src/react/Tile.js | 33 ++ src/react/TileFooter.js | 10 + src/react/TileFooterCell.js | 7 + src/react/TileInfo.js | 10 + src/react/TileInfoLine.js | 7 + src/react/index.js | 74 +++ src/style/scss/_common.scss | 7 + src/style/scss/_components.scss | 22 + src/style/scss/angular/_svg_icon.scss | 210 ++++++++ src/style/scss/angular/_tooltip_custom_style.scss | 9 + src/style/scss/common/_animation.scss | 149 ++++++ src/style/scss/common/_icons.scss | 19 + src/style/scss/common/_normalize.scss | 578 +++++++++++++++++++++ src/style/scss/common/_typography.scss | 96 ++++ src/style/scss/common/base.scss | 96 ++++ src/style/scss/common/mixins.scss | 337 ++++++++++++ src/style/scss/common/variables.scss | 35 ++ src/style/scss/style.scss | 6 + src/style/scss/themes/1802/_components.scss | 23 + src/style/scss/themes/1802/button.scss | 148 ++++++ src/style/scss/themes/1802/modal.scss | 193 +++++++ src/style/scss/themes/1802/style.scss | 5 + src/style/scss/themes/1802/tabs.scss | 39 ++ 147 files changed, 6687 insertions(+) create mode 100644 src/README.md create mode 100644 src/angular/accordion/accordion.component.html.ts create mode 100644 src/angular/accordion/accordion.component.ts create mode 100644 src/angular/accordion/accordion.module.ts create mode 100644 src/angular/animations/animation-directives.module.ts create mode 100644 src/angular/animations/ripple-click.animation.directive.ts create mode 100644 src/angular/autocomplete/autocomplete.component.html.ts create mode 100644 src/angular/autocomplete/autocomplete.component.ts create mode 100644 src/angular/autocomplete/autocomplete.module.ts create mode 100644 src/angular/autocomplete/autocomplete.pipe.ts create mode 100644 src/angular/buttons/button.component.html.ts create mode 100644 src/angular/buttons/button.component.ts create mode 100644 src/angular/buttons/buttons.module.ts create mode 100644 src/angular/checklist/checklist.component.html.ts create mode 100644 src/angular/checklist/checklist.component.ts create mode 100644 src/angular/checklist/checklist.module.ts create mode 100644 src/angular/checklist/models/Checklist.ts create mode 100644 src/angular/checklist/models/ChecklistItem.ts create mode 100644 src/angular/common/enums.ts create mode 100644 src/angular/common/index.ts create mode 100644 src/angular/components.ts create mode 100644 src/angular/filterbar/filter-bar.component.html.ts create mode 100644 src/angular/filterbar/filter-bar.component.ts create mode 100644 src/angular/filterbar/filter-bar.module.ts create mode 100644 src/angular/form-elements/checkbox/checkbox.component.html.ts create mode 100644 src/angular/form-elements/checkbox/checkbox.component.spec.ts create mode 100644 src/angular/form-elements/checkbox/checkbox.component.ts create mode 100644 src/angular/form-elements/dropdown/dropdown-models.ts create mode 100644 src/angular/form-elements/dropdown/dropdown-trigger.directive.ts create mode 100644 src/angular/form-elements/dropdown/dropdown.component.html.ts create mode 100644 src/angular/form-elements/dropdown/dropdown.component.spec.ts create mode 100644 src/angular/form-elements/dropdown/dropdown.component.ts create mode 100644 src/angular/form-elements/form-elements.module.ts create mode 100644 src/angular/form-elements/input/input.component.html.ts create mode 100644 src/angular/form-elements/input/input.component.ts create mode 100644 src/angular/form-elements/radios/radio-button.model.ts create mode 100644 src/angular/form-elements/radios/radio-buttons-group.component.html.ts create mode 100644 src/angular/form-elements/radios/radio-buttons-group.component.spec.ts create mode 100644 src/angular/form-elements/radios/radio-buttons-group.component.ts create mode 100644 src/angular/form-elements/validation/validatable.component.ts create mode 100644 src/angular/form-elements/validation/validatable.interface.ts create mode 100644 src/angular/form-elements/validation/validation-group.component.html.ts create mode 100644 src/angular/form-elements/validation/validation-group.component.ts create mode 100644 src/angular/form-elements/validation/validation.component.html.ts create mode 100644 src/angular/form-elements/validation/validation.component.ts create mode 100644 src/angular/form-elements/validation/validation.module.ts create mode 100644 src/angular/form-elements/validation/validators/base.validator.component.html.ts create mode 100644 src/angular/form-elements/validation/validators/base.validator.component.ts create mode 100644 src/angular/form-elements/validation/validators/custom.validator.component.ts create mode 100644 src/angular/form-elements/validation/validators/regex.validator.component.ts create mode 100644 src/angular/form-elements/validation/validators/required.validator.component.ts create mode 100644 src/angular/form-elements/validation/validators/validator.interface.ts create mode 100644 src/angular/index.ts create mode 100644 src/angular/infinite-scroll/infinite-scroll.directive.ts create mode 100644 src/angular/infinite-scroll/infinite-scroll.module.ts create mode 100644 src/angular/modals/modal-button.component.ts create mode 100644 src/angular/modals/modal-close-button.component.ts create mode 100644 src/angular/modals/modal.component.html.ts create mode 100644 src/angular/modals/modal.component.spec.ts create mode 100644 src/angular/modals/modal.component.ts create mode 100644 src/angular/modals/modal.module.ts create mode 100644 src/angular/modals/modal.service.ts create mode 100644 src/angular/modals/models/modal-config.ts create mode 100644 src/angular/ng1.module.ts create mode 100644 src/angular/notifications/container/notifcontainer.component.html.ts create mode 100644 src/angular/notifications/container/notifcontainer.component.ts create mode 100644 src/angular/notifications/notification-inner-content-example.component.ts create mode 100644 src/angular/notifications/notification.module.ts create mode 100644 src/angular/notifications/notification/notification.component.html.ts create mode 100644 src/angular/notifications/notification/notification.component.ts create mode 100644 src/angular/notifications/services/notifications.service.ts create mode 100644 src/angular/notifications/utilities/notification.config.ts create mode 100644 src/angular/popup-menu/popup-menu-item.component.spec.ts create mode 100644 src/angular/popup-menu/popup-menu-item.component.ts create mode 100644 src/angular/popup-menu/popup-menu-list.component.ts create mode 100644 src/angular/popup-menu/popup-menu.module.ts create mode 100644 src/angular/searchbar/search-bar.component.html.ts create mode 100644 src/angular/searchbar/search-bar.component.ts create mode 100644 src/angular/searchbar/search-bar.module.ts create mode 100644 src/angular/svg-icon/svg-icon-label.component.html.ts create mode 100644 src/angular/svg-icon/svg-icon-label.component.ts create mode 100644 src/angular/svg-icon/svg-icon.component.html.ts create mode 100644 src/angular/svg-icon/svg-icon.component.ts create mode 100644 src/angular/svg-icon/svg-icon.module.ts create mode 100644 src/angular/tabs/children/tab.component.html.ts create mode 100644 src/angular/tabs/children/tab.component.ts create mode 100644 src/angular/tabs/tabs.component.html.ts create mode 100644 src/angular/tabs/tabs.component.ts create mode 100644 src/angular/tabs/tabs.module.ts create mode 100644 src/angular/tag-cloud/tag-cloud.component.html.ts create mode 100644 src/angular/tag-cloud/tag-cloud.component.ts create mode 100644 src/angular/tag-cloud/tag-cloud.module.ts create mode 100644 src/angular/tag-cloud/tag-item/tag-item.component.html.ts create mode 100644 src/angular/tag-cloud/tag-item/tag-item.component.ts create mode 100644 src/angular/tiles/children/tile-content.component.ts create mode 100644 src/angular/tiles/children/tile-footer.component.ts create mode 100644 src/angular/tiles/children/tile-header.component.ts create mode 100644 src/angular/tiles/tile.component.html.ts create mode 100644 src/angular/tiles/tile.component.ts create mode 100644 src/angular/tiles/tile.module.ts create mode 100644 src/angular/tooltip/tooltip-template.component.ts create mode 100644 src/angular/tooltip/tooltip.directive.ts create mode 100644 src/angular/tooltip/tooltip.module.ts create mode 100644 src/angular/utils/create-dynamic-component.service.ts create mode 100644 src/react/Accordion.js create mode 100644 src/react/Button.js create mode 100644 src/react/Checkbox.js create mode 100644 src/react/Checklist.js create mode 100644 src/react/Input.js create mode 100644 src/react/Modal.js create mode 100644 src/react/ModalBody.js create mode 100644 src/react/ModalFooter.js create mode 100644 src/react/ModalHeader.js create mode 100644 src/react/ModalTitle.js create mode 100644 src/react/Panel.js create mode 100644 src/react/PopupMenu.js create mode 100644 src/react/PopupMenuItem.js create mode 100644 src/react/Portal.js create mode 100644 src/react/Radio.js create mode 100644 src/react/RadioGroup.js create mode 100644 src/react/SVGIcon.js create mode 100644 src/react/Tab.js create mode 100644 src/react/TabPane.js create mode 100644 src/react/Tabs.js create mode 100644 src/react/Tile.js create mode 100644 src/react/TileFooter.js create mode 100644 src/react/TileFooterCell.js create mode 100644 src/react/TileInfo.js create mode 100644 src/react/TileInfoLine.js create mode 100644 src/react/index.js create mode 100644 src/style/scss/_common.scss create mode 100644 src/style/scss/_components.scss create mode 100644 src/style/scss/angular/_svg_icon.scss create mode 100644 src/style/scss/angular/_tooltip_custom_style.scss create mode 100644 src/style/scss/common/_animation.scss create mode 100644 src/style/scss/common/_icons.scss create mode 100644 src/style/scss/common/_normalize.scss create mode 100644 src/style/scss/common/_typography.scss create mode 100644 src/style/scss/common/base.scss create mode 100644 src/style/scss/common/mixins.scss create mode 100644 src/style/scss/common/variables.scss create mode 100644 src/style/scss/style.scss create mode 100644 src/style/scss/themes/1802/_components.scss create mode 100644 src/style/scss/themes/1802/button.scss create mode 100644 src/style/scss/themes/1802/modal.scss create mode 100644 src/style/scss/themes/1802/style.scss create mode 100644 src/style/scss/themes/1802/tabs.scss (limited to 'src') diff --git a/src/README.md b/src/README.md new file mode 100644 index 0000000..83d47bf --- /dev/null +++ b/src/README.md @@ -0,0 +1,9 @@ + +# Folder Structure + +### Angular 2 Framework +Contains **Angular 2** based components + + +### React Framework +Contains **React.js** based components diff --git a/src/angular/accordion/accordion.component.html.ts b/src/angular/accordion/accordion.component.html.ts new file mode 100644 index 0000000..ac5f81f --- /dev/null +++ b/src/angular/accordion/accordion.component.html.ts @@ -0,0 +1,21 @@ +export default ` +
+
+
+ + + + + + +
+
+ {{title}} +
+
+
+ +
+
`; diff --git a/src/angular/accordion/accordion.component.ts b/src/angular/accordion/accordion.component.ts new file mode 100644 index 0000000..b16df89 --- /dev/null +++ b/src/angular/accordion/accordion.component.ts @@ -0,0 +1,27 @@ +/** + * Created by M.S.BIT on 26/04/2018. + */ + +import {Component, Input, Output, EventEmitter} from "@angular/core"; +import {Placement} from "../common/enums"; +import template from './accordion.component.html'; + +@Component({ + selector: 'sdc-accordion', + template: template, +}) +export class AccordionComponent { + + @Input('arrow-direction') arrowDirection: Placement; + @Input('css-class') customCSSClass: string; + @Input('title') title: string; + @Input('open') open: boolean; + @Output('accordionChanged') changed = new EventEmitter(); + + public accordionArrowDirection = Placement; + + public toggleAccordion(){ + this.open = !this.open; + this.changed.emit(this.open); + } +} diff --git a/src/angular/accordion/accordion.module.ts b/src/angular/accordion/accordion.module.ts new file mode 100644 index 0000000..6cda646 --- /dev/null +++ b/src/angular/accordion/accordion.module.ts @@ -0,0 +1,23 @@ +/** + * Created by M.S.BIT on 26/04/2018. + */ +import { NgModule } from "@angular/core"; +import { CommonModule } from "@angular/common"; +import {AccordionComponent} from "./accordion.component"; +import {SvgIconModule} from "../svg-icon/svg-icon.module"; + +@NgModule({ + declarations: [ + AccordionComponent + ], + imports: [ + CommonModule, + SvgIconModule + ], + exports: [ + AccordionComponent + ], +}) +export class AccordionModule { + +} diff --git a/src/angular/animations/animation-directives.module.ts b/src/angular/animations/animation-directives.module.ts new file mode 100644 index 0000000..c6a8203 --- /dev/null +++ b/src/angular/animations/animation-directives.module.ts @@ -0,0 +1,19 @@ +import { NgModule } from "@angular/core"; +import { RippleClickAnimationDirective } from "./ripple-click.animation.directive"; +import { CommonModule } from "@angular/common"; + +@NgModule({ + declarations: [ + RippleClickAnimationDirective + ], + imports: [ + CommonModule + ], + exports: [ + RippleClickAnimationDirective + + ], +}) +export class AnimationDirectivesModule { + +} diff --git a/src/angular/animations/ripple-click.animation.directive.ts b/src/angular/animations/ripple-click.animation.directive.ts new file mode 100644 index 0000000..7b9ed55 --- /dev/null +++ b/src/angular/animations/ripple-click.animation.directive.ts @@ -0,0 +1,47 @@ +import { Directive, Input, HostBinding, HostListener } from "@angular/core"; + +export enum RippleAnimationAction { + CLICK = 0, + MOUSE_ENTER = 1 +}; + +@Directive({ + selector: `[SdcRippleClickAnimation]` +}) +export class RippleClickAnimationDirective { + private animated: boolean; + + @Input() rippleClickDisabled: boolean; + @Input() rippleOnAction:RippleAnimationAction = RippleAnimationAction.CLICK; + + @HostBinding('class.sdc-ripple-click__animated') animationClass: string; + + @HostListener('click') onClick() { + if(this.rippleOnAction === RippleAnimationAction.CLICK){ + this.animateStart(); + } + } + + @HostListener('mouseenter') onMouseEnter() { + //console.log("Mouseenter!", this.rippleOnAction); + if(this.rippleOnAction === RippleAnimationAction.MOUSE_ENTER){ + this.animateStart(); + } + } + + private animateStart():void{ + if (!this.rippleClickDisabled) { + this.animated = true; + this.animationClass = 'sdc-ripple-click__animated'; + } + } + @HostListener('animationend') onAnimationComplete() { + this.animated = false; + this.animationClass = ''; + } + + constructor() { + this.rippleClickDisabled = false; + this.animated = false; + } +} diff --git a/src/angular/autocomplete/autocomplete.component.html.ts b/src/angular/autocomplete/autocomplete.component.html.ts new file mode 100644 index 0000000..5df7352 --- /dev/null +++ b/src/angular/autocomplete/autocomplete.component.html.ts @@ -0,0 +1,14 @@ +export default ` +
+ + +
    +
  • {{item.value}}
  • +
+
+`; diff --git a/src/angular/autocomplete/autocomplete.component.ts b/src/angular/autocomplete/autocomplete.component.ts new file mode 100644 index 0000000..5570eff --- /dev/null +++ b/src/angular/autocomplete/autocomplete.component.ts @@ -0,0 +1,114 @@ +import { OnInit, animate, Component, EventEmitter, Input, Output, state, style, transition, trigger } from '@angular/core'; +import { FilterBarComponent } from "../filterbar/filter-bar.component"; +import { URLSearchParams, Http } from "@angular/http"; +import { AutocompletePipe } from "./autocomplete.pipe"; +import template from "./autocomplete.component.html"; +import 'rxjs/add/operator/map'; + +export interface IDataSchema { + key: string; + value: string; +} + +@Component({ + selector: 'sdc-autocomplete', + template: template, + animations: [ + trigger('displayResultsAnimation', [ + state('true', style({ + height: '*', + opacity: 1 + })), + state('false', style({ + height: 0, + opacity: 0 + })), + transition('* => *', animate('200ms')) + ]), + ], + providers: [AutocompletePipe] +}) +export class SearchWithAutoCompleteComponent implements OnInit { + @Input() public data: any[] = []; + @Input() public dataSchema: IDataSchema; + @Input() public dataUrl: string; + @Input() public label: string; + @Input() public placeholder: string; + @Output() public itemSelected: EventEmitter = new EventEmitter(); + + private searchQuery: string; + private complexData: any[] = []; + private autoCompleteResults: any[] = []; + private isItemSelected: boolean = false; + + public constructor(private http: Http, private autocompletePipe: AutocompletePipe) { + } + + public ngOnInit(): void { + if (this.data) { + this.handleLocalData(); + } + this.searchQuery = ""; + } + + private handleLocalData = (): void => { + // Convert the data (simple | complex) to unified complex data with key value. + // In case user supplied dataSchema, this is complex data + if (!this.dataSchema) { + this.convertSimpleData(); + } else { + this.convertComplexData(); + } + } + + private convertSimpleData = (): void => { + this.complexData = []; + this.data.forEach((item: any) => { + this.complexData.push({key: item, value: item}); + }); + } + + private convertComplexData = (): void => { + this.complexData = []; + this.data.forEach((item: any) => { + this.complexData.push({key: item[this.dataSchema.key], value: item[this.dataSchema.value]}); + }); + } + + private onItemSelected = (selectedItem: IDataSchema): void => { + this.searchQuery = selectedItem.value; + this.isItemSelected = true; + this.autoCompleteResults = []; + this.itemSelected.emit(selectedItem.key); + } + + private onSearchQueryChanged = (searchText: string): void => { + if (searchText !== this.searchQuery) { + this.searchQuery = searchText; + if (!this.searchQuery) { + this.onClearSearch(); + } else { + if (this.dataUrl) { + const params: URLSearchParams = new URLSearchParams(); + params.set('searchQuery', this.searchQuery); + this.http.get(this.dataUrl, {search: params}) + .map((response) => { + this.data = response.json(); + this.handleLocalData(); + this.autoCompleteResults = this.complexData; + }).subscribe(); + } else { + this.autoCompleteResults = this.autocompletePipe.transform(this.complexData, this.searchQuery); + } + } + this.isItemSelected = false; + } + } + + private onClearSearch = (): void => { + this.autoCompleteResults = []; + if (this.isItemSelected) { + this.itemSelected.emit(); + } + } +} diff --git a/src/angular/autocomplete/autocomplete.module.ts b/src/angular/autocomplete/autocomplete.module.ts new file mode 100644 index 0000000..1bead47 --- /dev/null +++ b/src/angular/autocomplete/autocomplete.module.ts @@ -0,0 +1,23 @@ +import { NgModule } from "@angular/core"; +import { SearchWithAutoCompleteComponent } from "./autocomplete.component"; +import { CommonModule } from "@angular/common"; +import { FilterBarModule } from "../filterbar/filter-bar.module"; +import { AutocompletePipe } from "./autocomplete.pipe"; +import { HttpModule } from '@angular/http'; + +@NgModule({ + declarations: [ + SearchWithAutoCompleteComponent, + AutocompletePipe + ], + imports: [ + FilterBarModule, + CommonModule, + HttpModule + ], + exports: [ + SearchWithAutoCompleteComponent + ], +}) +export class AutoCompleteModule { +} diff --git a/src/angular/autocomplete/autocomplete.pipe.ts b/src/angular/autocomplete/autocomplete.pipe.ts new file mode 100644 index 0000000..bee24ab --- /dev/null +++ b/src/angular/autocomplete/autocomplete.pipe.ts @@ -0,0 +1,16 @@ +import { Pipe, PipeTransform } from '@angular/core'; +import { IDataSchema } from './autocomplete.component'; + +@Pipe ({ + name: 'AutocompletePipe', +}) +export class AutocompletePipe implements PipeTransform { + public transform(data: IDataSchema[], text: string) { + if (!text || !text.length) { + return data; + } + return data.filter((item: IDataSchema) => { + return item.value.toLowerCase().indexOf(text.toLowerCase()) > -1; + }); + } +} diff --git a/src/angular/buttons/button.component.html.ts b/src/angular/buttons/button.component.html.ts new file mode 100644 index 0000000..f903fd1 --- /dev/null +++ b/src/angular/buttons/button.component.html.ts @@ -0,0 +1,15 @@ +export default ` + + +`; diff --git a/src/angular/buttons/button.component.ts b/src/angular/buttons/button.component.ts new file mode 100644 index 0000000..1f049dc --- /dev/null +++ b/src/angular/buttons/button.component.ts @@ -0,0 +1,62 @@ +import { Component, HostBinding, Input, OnInit } from "@angular/core"; +import { Placement } from "../common/enums"; +import template from "./button.component.html"; + +@Component({ + selector: "sdc-button", + template: template +}) + +export class ButtonComponent implements OnInit { + @Input() public text: string; + @Input() public disabled: boolean; + @Input() public type: string; + @Input() public size: string; + @Input() public preventDoubleClick: boolean; + @Input() public icon_name: string; + @Input() public icon_position: string; + @Input() public show_spinner: boolean; + @Input() public spinner_position: Placement; + @Input() public testId: string; + + public placement = Placement; + private lastClick: Date; + private iconPositionClass: string; + private iconMode: string; + + @HostBinding('class.sdc-button__wrapper') true; + + constructor() { + this.type = "primary"; + this.size = "default"; + this.disabled = false; + this.iconMode = 'primary'; + } + + public ngOnInit(): void { + this.iconPositionClass = this.icon_position ? 'sdc-icon-' + this.icon_position : ''; + this.iconMode = (this.type === "primary") ? 'info' : 'primary'; + } + + public onClick = (e): void => { + const now: Date = new Date(); + if ( this.preventDoubleClick && this.lastClick && (now.getTime() - this.lastClick.getTime()) <= 500 ) { + e.preventDefault(); + e.stopPropagation(); + } + this.lastClick = now; + } + + public disableButton = () => { + if (!this.disabled) { + this.disabled = true; + } + } + + public enableButton = () => { + if (this.disabled) { + this.disabled = false; + } + } + +} diff --git a/src/angular/buttons/buttons.module.ts b/src/angular/buttons/buttons.module.ts new file mode 100644 index 0000000..c804758 --- /dev/null +++ b/src/angular/buttons/buttons.module.ts @@ -0,0 +1,21 @@ +import { NgModule } from "@angular/core"; +import { ButtonComponent } from "./button.component"; +import { CommonModule } from "@angular/common"; +import { SvgIconModule } from './../svg-icon/svg-icon.module'; + +@NgModule({ + declarations: [ + ButtonComponent + ], + imports: [ + CommonModule, + SvgIconModule + ], + exports: [ + ButtonComponent + + ], +}) +export class ButtonsModule { + +} diff --git a/src/angular/checklist/checklist.component.html.ts b/src/angular/checklist/checklist.component.html.ts new file mode 100644 index 0000000..cb6f540 --- /dev/null +++ b/src/angular/checklist/checklist.component.html.ts @@ -0,0 +1,15 @@ +export default ` +
+
+ +
+
+ +
+
+`; diff --git a/src/angular/checklist/checklist.component.ts b/src/angular/checklist/checklist.component.ts new file mode 100644 index 0000000..386cd3e --- /dev/null +++ b/src/angular/checklist/checklist.component.ts @@ -0,0 +1,50 @@ +import { Component, EventEmitter, Input, Output } from "@angular/core"; +import { ChecklistModel } from "./models/Checklist"; +import { ChecklistItemModel } from "./models/ChecklistItem"; +import template from "./checklist.component.html"; + +@Component({ + selector: 'sdc-checklist', + template: template +}) +export class ChecklistComponent { + @Input() public checklistModel: ChecklistModel; + @Output() public checkedChange: EventEmitter = new EventEmitter(); + + private checkboxCheckedChange(checkbox: ChecklistItemModel, currentChecklistModel: ChecklistModel, stopPropagation?: boolean) { + // push/pop the checkbox value + if (checkbox.isChecked) { + currentChecklistModel.selectedValues.push(checkbox.value); + }else { + const index: number = currentChecklistModel.selectedValues.indexOf(checkbox.value); + currentChecklistModel.selectedValues.splice(index, 1); + } + if (!stopPropagation) { + if (checkbox.subLevelChecklist && + ((checkbox.isChecked && checkbox.subLevelChecklist.selectedValues.length < checkbox.subLevelChecklist.checkboxes.length) || + (!checkbox.isChecked && checkbox.subLevelChecklist.selectedValues.length))) { + checkbox.subLevelChecklist.checkboxes.forEach((childCheckbox: ChecklistItemModel) => { + if (childCheckbox.isChecked !== checkbox.isChecked) { + childCheckbox.isChecked = checkbox.isChecked; + this.checkboxCheckedChange(childCheckbox, checkbox.subLevelChecklist); + } + }); + } + } + // raise event + this.checkedChange.emit(checkbox); + } + + private childCheckboxChange(updatedCheckbox: ChecklistItemModel, parentCheckbox: ChecklistItemModel) { + let updatedValues: any[] = parentCheckbox.subLevelChecklist.selectedValues; + if (parentCheckbox.isChecked !== (updatedValues.length === parentCheckbox.subLevelChecklist.checkboxes.length)) { + parentCheckbox.isChecked = updatedValues.length === parentCheckbox.subLevelChecklist.checkboxes.length; + this.checkboxCheckedChange(parentCheckbox, this.checklistModel, true); + } + this.checkedChange.emit(updatedCheckbox); + } + + private hasCheckedChild(currentCheckbox: Element): boolean { + return !!currentCheckbox.querySelector(".sdc-checkbox__input:checked"); + } +} diff --git a/src/angular/checklist/checklist.module.ts b/src/angular/checklist/checklist.module.ts new file mode 100644 index 0000000..013bf9b --- /dev/null +++ b/src/angular/checklist/checklist.module.ts @@ -0,0 +1,11 @@ +import { NgModule } from "@angular/core"; +import { ChecklistComponent } from "./checklist.component"; +import { CommonModule } from "@angular/common"; +import { FormElementsModule } from "../form-elements/form-elements.module"; + +@NgModule({ + declarations: [ChecklistComponent], + exports: [ChecklistComponent], + imports: [CommonModule, FormElementsModule] +}) +export class ChecklistModule {} diff --git a/src/angular/checklist/models/Checklist.ts b/src/angular/checklist/models/Checklist.ts new file mode 100644 index 0000000..7b50dd3 --- /dev/null +++ b/src/angular/checklist/models/Checklist.ts @@ -0,0 +1,18 @@ +import { ChecklistItemModel } from "./ChecklistItem"; + +export class ChecklistModel { + public selectedValues: any[]; + public checkboxes: ChecklistItemModel[]; + constructor(selectedValues: any[], checkboxes: ChecklistItemModel[]) { + this.selectedValues = selectedValues || []; + this.checkboxes = checkboxes; + // align the selected values list and checkboxes isChecked param + this.checkboxes.forEach((checkbox: ChecklistItemModel) => { + if (this.selectedValues.indexOf(checkbox.value) > -1) { + checkbox.isChecked = true; + }else if (checkbox.isChecked) { + this.selectedValues.push(checkbox.value); + } + }); + } +} diff --git a/src/angular/checklist/models/ChecklistItem.ts b/src/angular/checklist/models/ChecklistItem.ts new file mode 100644 index 0000000..e2d812a --- /dev/null +++ b/src/angular/checklist/models/ChecklistItem.ts @@ -0,0 +1,17 @@ +import { ChecklistModel } from "./Checklist"; +import { isUndefined } from "util"; + +export class ChecklistItemModel { + public label: string; + public value: any; + public disabled: boolean; + public isChecked: boolean; + public subLevelChecklist: ChecklistModel; + constructor(label: string, disabled?: boolean, isChecked?: boolean, subLevelChecklist?: ChecklistModel, value?: any) { + this.label = label; + this.disabled = disabled; + this.isChecked = isChecked; + this.value = isUndefined(value) ? label : value; + this.subLevelChecklist = subLevelChecklist; + } +} diff --git a/src/angular/common/enums.ts b/src/angular/common/enums.ts new file mode 100644 index 0000000..0825d2f --- /dev/null +++ b/src/angular/common/enums.ts @@ -0,0 +1,34 @@ +/* +This file includes all common enum types. + +NOTE: The string values might be used as css class names. +*/ + +export enum Size { + x_large = 'x_large', + large = 'large', + medium = 'medium', + small = 'small', + x_small = 'x_small' +} + +export enum Mode { + primary = 'primary', + secondary = 'secondary', + success = 'success', + error = 'error', + warning = 'warning', + info = 'info' +} + +export enum Placement { + left = 'left', + right = 'right', + top = 'top', + bottom = 'bottom' +} + +export enum RegexPatterns { + email = '^(([^<>()\\[\\]\\\\.,;:\\s@"]+(\.[^<>()\\[\\]\\\\.,;:\\s@"]+)*)|(".+"))@((\\[[0-9]{1,3}\\.[0-9]{1,3}\\.[0-9]{1,3}\\.[0-9]{1,3}\\])|(([a-zA-Z\\-0-9]+\\.)+[a-zA-Z]{2,}))$', + numbers = '^\\d+$' +} diff --git a/src/angular/common/index.ts b/src/angular/common/index.ts new file mode 100644 index 0000000..839eba9 --- /dev/null +++ b/src/angular/common/index.ts @@ -0,0 +1,3 @@ +import * as SdcUiCommon from './enums'; + +export { SdcUiCommon }; diff --git a/src/angular/components.ts b/src/angular/components.ts new file mode 100644 index 0000000..8e06197 --- /dev/null +++ b/src/angular/components.ts @@ -0,0 +1,50 @@ +/* + Exports all the components and services of sdc-ui. + */ + +// Form Elements +export { InputComponent } from "./form-elements/input/input.component"; +export { DropDownComponent } from "./form-elements/dropdown/dropdown.component"; +export { CheckboxComponent } from "./form-elements/checkbox/checkbox.component"; +export { RadioGroupComponent } from "./form-elements/radios/radio-buttons-group.component"; + +// Buttons +export { ButtonComponent } from "./buttons/button.component"; + +// Modals +export { ModalComponent } from "./modals/modal.component"; +export { ModalService } from "./modals/modal.service"; +export { ModalButtonComponent } from "./modals/modal-button.component"; + +// Notifications +export { NotificationComponent } from "./notifications/notification/notification.component"; +export { NotificationContainerComponent } from "./notifications/container/notifcontainer.component"; +export { NotificationsService } from "./notifications/services/notifications.service"; + +// Popup Menu +export { PopupMenuListComponent } from "./popup-menu/popup-menu-list.component"; +export { PopupMenuItemComponent } from "./popup-menu/popup-menu-item.component"; + +// Tiles +export { TileComponent } from "./tiles/tile.component"; +export { TileContentComponent } from "./tiles/children/tile-content.component"; +export { TileFooterComponent } from "./tiles/children/tile-footer.component"; +export { TileHeaderComponent } from "./tiles/children/tile-header.component"; + +// Check List +export { ChecklistComponent } from "./checklist/checklist.component"; + +// Tag Cloud +export { TagItemComponent } from "./tag-cloud/tag-item/tag-item.component"; +export { TagCloudComponent } from "./tag-cloud/tag-cloud.component"; + +// Tabs +export { TabsComponent } from "./tabs/tabs.component"; +export { TabComponent } from './tabs/children/tab.component'; + +// Svg Icons +export { SvgIconComponent } from "./svg-icon/svg-icon.component"; +export { SvgIconLabelComponent } from "./svg-icon/svg-icon-label.component"; + +// Accordion +export { AccordionComponent } from './accordion/accordion.component'; diff --git a/src/angular/filterbar/filter-bar.component.html.ts b/src/angular/filterbar/filter-bar.component.html.ts new file mode 100644 index 0000000..a7d55e2 --- /dev/null +++ b/src/angular/filterbar/filter-bar.component.html.ts @@ -0,0 +1,30 @@ +export default ` +
+ + + + + + + + + + + + + + + + + + + + + +
+`; diff --git a/src/angular/filterbar/filter-bar.component.ts b/src/angular/filterbar/filter-bar.component.ts new file mode 100644 index 0000000..49cc154 --- /dev/null +++ b/src/angular/filterbar/filter-bar.component.ts @@ -0,0 +1,30 @@ +import { Component, Input, Output, EventEmitter, HostBinding } from '@angular/core'; +import template from "./filter-bar.component.html"; + +@Component({ + selector: 'sdc-filter-bar', + template: template +}) +export class FilterBarComponent { + + @HostBinding('class') classes = 'sdc-filter-bar'; + + @Input() public placeholder: string; + @Input() public label: string; + @Input() public debounceTime: number; + + @Input() public searchQuery: string; + @Output() public searchQueryChange: EventEmitter = new EventEmitter(); + + constructor() { + this.debounceTime = 200; + } + + private searchTextChange = ($event): void => { + this.searchQueryChange.emit($event); + } + + private clearSearchQuery = (): void => { + this.searchQuery = ""; + } +} diff --git a/src/angular/filterbar/filter-bar.module.ts b/src/angular/filterbar/filter-bar.module.ts new file mode 100644 index 0000000..c3604ed --- /dev/null +++ b/src/angular/filterbar/filter-bar.module.ts @@ -0,0 +1,17 @@ +import { NgModule } from "@angular/core"; +import { FilterBarComponent } from "./filter-bar.component"; +import { CommonModule } from "@angular/common"; +import { FormElementsModule } from "../form-elements/form-elements.module"; + +@NgModule({ + declarations: [ + FilterBarComponent + ], + imports: [CommonModule, + FormElementsModule], + exports: [ + FilterBarComponent + ], +}) +export class FilterBarModule { +} diff --git a/src/angular/form-elements/checkbox/checkbox.component.html.ts b/src/angular/form-elements/checkbox/checkbox.component.html.ts new file mode 100644 index 0000000..f4031db --- /dev/null +++ b/src/angular/form-elements/checkbox/checkbox.component.html.ts @@ -0,0 +1,8 @@ +export default ` +
+ +
+`; diff --git a/src/angular/form-elements/checkbox/checkbox.component.spec.ts b/src/angular/form-elements/checkbox/checkbox.component.spec.ts new file mode 100644 index 0000000..36f478e --- /dev/null +++ b/src/angular/form-elements/checkbox/checkbox.component.spec.ts @@ -0,0 +1,37 @@ +import { TestBed, async } from '@angular/core/testing'; +import { CheckboxComponent } from "./checkbox.component"; +import { AnimationDirectivesModule } from "../../animations/animation-directives.module"; +import { FormsModule } from "@angular/forms"; + + +describe("Checbox Tests", ()=>{ + let component: CheckboxComponent; + beforeEach(async(() => { + TestBed.configureTestingModule({ + declarations: [ + CheckboxComponent + ], + imports:[ + FormsModule, + AnimationDirectivesModule + ] + }).compileComponents(); + const fixture = TestBed.createComponent(CheckboxComponent); + component = fixture.componentInstance; + })); + + it("Component Created", async(()=> { + expect(component).toBeDefined(); + })); + + it( "Test Value suppose to be toggled", async( ()=> { + component.toggleState(true) + expect(component.checked).toEqual(true); + })); + + it( "If disabled not toggled"), async(()=>{ + component.disabled = true; + component.toggleState(true); + expect(component.checked).toEqual(false); + }); +}); diff --git a/src/angular/form-elements/checkbox/checkbox.component.ts b/src/angular/form-elements/checkbox/checkbox.component.ts new file mode 100644 index 0000000..ec05eac --- /dev/null +++ b/src/angular/form-elements/checkbox/checkbox.component.ts @@ -0,0 +1,21 @@ +import { Component, Input, Output, EventEmitter, ViewEncapsulation } from '@angular/core'; +import template from "./checkbox.component.html"; + +@Component({ + selector: 'sdc-checkbox', + template: template, + encapsulation: ViewEncapsulation.None +}) +export class CheckboxComponent { + @Input() label:string; + @Input() checked:boolean; + @Input() disabled:boolean; + @Output() checkedChange:EventEmitter = new EventEmitter(); + + public toggleState(newState:boolean) { + if (!this.disabled) { + this.checked = newState; + this.checkedChange.emit(newState); + } + } +} diff --git a/src/angular/form-elements/dropdown/dropdown-models.ts b/src/angular/form-elements/dropdown/dropdown-models.ts new file mode 100644 index 0000000..fa8dc23 --- /dev/null +++ b/src/angular/form-elements/dropdown/dropdown-models.ts @@ -0,0 +1,18 @@ +export enum DropDownTypes { + Regular, + Headless, + Auto +} + +export enum DropDownOptionType { + Simple, // default + Header, + Disable, + HorizontalLine +} + +export interface IDropDownOption { + value: any; + label: string; + type?: DropDownOptionType; +} diff --git a/src/angular/form-elements/dropdown/dropdown-trigger.directive.ts b/src/angular/form-elements/dropdown/dropdown-trigger.directive.ts new file mode 100644 index 0000000..94ab3bc --- /dev/null +++ b/src/angular/form-elements/dropdown/dropdown-trigger.directive.ts @@ -0,0 +1,17 @@ +import { Directive, Input, HostBinding, HostListener } from "@angular/core"; +import { DropDownComponent } from "./dropdown.component"; + +@Directive({ + selector: '[SdcDropdownTrigger]' +}) + +export class DropDownTriggerDirective { + + @HostBinding('class.js-sdc-dropdown--toggle-hook') true; + @Input() dropDown: DropDownComponent; + + @HostListener('click', ['$event']) onClick = (event) => { + this.dropDown.toggleDropdown(event); + } + +} diff --git a/src/angular/form-elements/dropdown/dropdown.component.html.ts b/src/angular/form-elements/dropdown/dropdown.component.html.ts new file mode 100644 index 0000000..a4247a4 --- /dev/null +++ b/src/angular/form-elements/dropdown/dropdown.component.html.ts @@ -0,0 +1,59 @@ +export default ` +
+ +
+ + +
+ + +
+ + + + + + + +
+
    + + +
  • {{option.label || String(option.value)}}
  • + +
    +
+
+ + +
+
+`; diff --git a/src/angular/form-elements/dropdown/dropdown.component.spec.ts b/src/angular/form-elements/dropdown/dropdown.component.spec.ts new file mode 100644 index 0000000..1c0cb4d --- /dev/null +++ b/src/angular/form-elements/dropdown/dropdown.component.spec.ts @@ -0,0 +1,71 @@ +import { async, ComponentFixture, TestBed } from '@angular/core/testing'; +import { DropDownComponent } from './dropdown.component'; +import { IDropDownOption, DropDownTypes } from "./dropdown-models"; +import { FormsModule } from "@angular/forms"; +import {SvgIconModule} from "../../svg-icon/svg-icon.module"; + + +const label:string = "DropDown example"; +const placeHolder:string = "Please choose option"; +const options:IDropDownOption[] = [ + { + label:'First Option', + value: 'First Option' + }, + { + label:'Second Option', + value: 'Second Option' + }, + { + label:'Third Option', + value: 'Third Option' + } +]; + +describe('DropDown component', () => { + let fixture: ComponentFixture; + let component: DropDownComponent; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + declarations: [ DropDownComponent ], + imports:[ + FormsModule, + SvgIconModule + ] + }).compileComponents(); + fixture = TestBed.createComponent(DropDownComponent); + component = fixture.componentInstance; + + })); + + beforeEach(()=>{ + component.label = label; + component.placeHolder = placeHolder; + component.options = options; + component.type = DropDownTypes.Regular; + console.log('herer we got component', component) + fixture.detectChanges(); + }); + + it('component should be created', () => { + expect(component).toBeTruthy(); + }); + + it('component should export the selected value', () => { + const option = options[1]; + component.selectOption(option); + fixture.detectChanges(); + expect(component.selectedOption).toEqual(option); + }); + + it('component should have autocomplite', () => { + expect(component.options.length).toEqual(3); + component.type = DropDownTypes.Auto; + component.filterValue = 'testERrorotesttresadfadfasdfasf'; + fixture.detectChanges(); + component.filterOptions(component.filterValue); + expect(component.options.length).toEqual(0); + }); + +}); diff --git a/src/angular/form-elements/dropdown/dropdown.component.ts b/src/angular/form-elements/dropdown/dropdown.component.ts new file mode 100644 index 0000000..a23072f --- /dev/null +++ b/src/angular/form-elements/dropdown/dropdown.component.ts @@ -0,0 +1,149 @@ +import { Component, EventEmitter, Input, Output, forwardRef, OnChanges, SimpleChanges, OnInit, ElementRef, ViewChild, AfterViewInit, HostListener, Renderer } from '@angular/core'; +import { IDropDownOption, DropDownOptionType, DropDownTypes } from "./dropdown-models"; +import { ValidatableComponent } from './../validation/validatable.component'; +import template from './dropdown.component.html'; + +@Component({ + selector: 'sdc-dropdown', + template: template +}) +export class DropDownComponent extends ValidatableComponent implements OnChanges, OnInit { + + @Output('changed') changeEmitter:EventEmitter = new EventEmitter(); + @Input() label: string; + @Input() options: IDropDownOption[]; + @Input() disabled: boolean; + @Input() placeHolder: string; + @Input() required: boolean; + @Input() maxHeight: number; + @Input() selectedOption: IDropDownOption; + @Input() type: DropDownTypes = DropDownTypes.Regular; + @ViewChild('dropDownWrapper') dropDownWrapper: ElementRef; + @ViewChild('optionsContainerElement') optionsContainerElement: ElementRef; + @HostListener('document:click', ['$event']) onClick(e) { + this.onClickDocument(e); + } + + private bottomVisible = true; + private myRenderer: Renderer; + + // Drop-down show/hide flag. default is false (closed) + public show = false; + + // Export DropDownOptionType enum so we can use it on the template + public cIDropDownOptionType = DropDownOptionType; + public cIDropDownTypes = DropDownTypes; + + // Configure unselectable option types + private unselectableOptions = [ + DropDownOptionType.Disable, + DropDownOptionType.Header, + DropDownOptionType.HorizontalLine + ]; + + // Set or unset Group style on drop-down + public isGroupDesign = false; + public animation_init = false; + public allOptions: IDropDownOption[]; + public filterValue: string; + + constructor(public renderer: Renderer) { + super(); + this.myRenderer = renderer; + this.maxHeight = 244; + this.filterValue = ''; + } + + ngOnInit(): void { + if (this.options) { + this.allOptions = this.options; + if (this.options.find(option => option.type === DropDownOptionType.Header)) { + this.isGroupDesign = true; + } + } + } + + ngOnChanges(changes: SimpleChanges): void { + console.log("ngOnChanges"); + if (changes.selectedOption && changes.selectedOption.currentValue !== changes.selectedOption.previousValue) { + if (typeof changes.selectedOption.currentValue === 'string' && this.isSelectable(changes.selectedOption.currentValue)) { + this.setSelected(changes.selectedOption.currentValue); + } else if (this.isSelectable(changes.selectedOption.currentValue.value)) { + this.setSelected(changes.selectedOption.currentValue.value); + } else { + this.setSelected(undefined); + } + } + } + + public getValue(): any { + return this.selectedOption && this.selectedOption.value; + } + + public selectOption = (option: IDropDownOption | string, event?): void => { + if (event) { event.stopPropagation(); } + if (this.type === DropDownTypes.Headless) { + // Hide the options when in headless mode and user select option. + this.myRenderer.setElementStyle(this.dropDownWrapper.nativeElement, 'display', 'none'); + } + if (typeof option === 'string' && this.isSelectable(option)) { + this.setSelected(option); + } else if (this.isSelectable((option as IDropDownOption).value)) { + this.setSelected((option as IDropDownOption).value); + } + } + + public toggleDropdown = (event?): void => { + if (event) { event.stopPropagation(); } + if (this.type === DropDownTypes.Headless) { + // Show the options when in headless mode. + this.myRenderer.setElementStyle(this.dropDownWrapper.nativeElement, 'display', 'block'); + } + if (this.disabled) { return; } + this.animation_init = true; + this.bottomVisible = this.isBottomVisible(); + this.show = !this.show; + } + + public filterOptions = (filterValue): void => { + if (filterValue.length >= 1 && !this.show) { this.toggleDropdown(); } + if (this.selectedOption) { this.selectedOption = null; } + this.options = this.allOptions.filter((option) => { + return option.value.toLowerCase().indexOf(filterValue.toLowerCase()) > -1; + }); + } + + private isSelectable = (value: string): boolean => { + const option: IDropDownOption = this.options.find(o => o.value === value); + if (!option) { return false; } + if (!option.type) { return true; } + return !this.unselectableOptions.find(optionType => optionType === option.type); + } + + private setSelected = (value: string): void => { + this.selectedOption = this.options.find(o => o.value === value); + if (this.type === DropDownTypes.Auto) { this.filterValue = value; } + this.show = false; + this.changeEmitter.next(this.selectedOption); + } + + private isBottomVisible = (): boolean => { + const windowPos = window.innerHeight + window.pageYOffset; + const boundingRect = this.dropDownWrapper.nativeElement.getBoundingClientRect(); + const dropDownPos = boundingRect.top + boundingRect.height + this.maxHeight; + return windowPos > dropDownPos; + } + + private onClickDocument = (event): void => { + if (this.type === DropDownTypes.Headless) { + if (!this.optionsContainerElement.nativeElement.contains(event.target)) { + this.show = false; + } + } else { + if (!this.dropDownWrapper.nativeElement.contains(event.target)) { + this.show = false; + } + } + } + +} diff --git a/src/angular/form-elements/form-elements.module.ts b/src/angular/form-elements/form-elements.module.ts new file mode 100644 index 0000000..744f8b8 --- /dev/null +++ b/src/angular/form-elements/form-elements.module.ts @@ -0,0 +1,38 @@ +import { NgModule } from "@angular/core"; +import { FormsModule, ReactiveFormsModule } from "@angular/forms"; +import { InputComponent } from "./input/input.component"; +import { DropDownComponent } from "./dropdown/dropdown.component"; +import { CommonModule } from "@angular/common"; +import { CheckboxComponent } from "./checkbox/checkbox.component"; +import { RadioGroupComponent } from "./radios/radio-buttons-group.component"; +import { AnimationDirectivesModule } from '../animations/animation-directives.module'; +import { DropDownTriggerDirective } from "./dropdown/dropdown-trigger.directive"; +import {SvgIconModule} from "../svg-icon/svg-icon.module"; +import { ValidationModule } from './validation/validation.module'; + +@NgModule({ + imports: [ + CommonModule, + FormsModule, + ReactiveFormsModule, + AnimationDirectivesModule, + SvgIconModule + ], + declarations: [ + DropDownComponent, + InputComponent, + CheckboxComponent, + RadioGroupComponent, + DropDownTriggerDirective, + ], + exports: [ + DropDownComponent, + DropDownTriggerDirective, + InputComponent, + CheckboxComponent, + RadioGroupComponent, + ValidationModule + ] +}) +export class FormElementsModule { +} diff --git a/src/angular/form-elements/input/input.component.html.ts b/src/angular/form-elements/input/input.component.html.ts new file mode 100644 index 0000000..f8a4609 --- /dev/null +++ b/src/angular/form-elements/input/input.component.html.ts @@ -0,0 +1,19 @@ +export default ` +
+ + +
+`; diff --git a/src/angular/form-elements/input/input.component.ts b/src/angular/form-elements/input/input.component.ts new file mode 100644 index 0000000..af0e9f4 --- /dev/null +++ b/src/angular/form-elements/input/input.component.ts @@ -0,0 +1,54 @@ +import { Component, EventEmitter, Input, OnInit, Output } from '@angular/core'; +import { FormControl } from "@angular/forms"; +import { ValidationComponent } from '../validation/validation.component'; +import { ValidatableComponent } from './../validation/validatable.component'; +import 'rxjs/add/operator/debounceTime'; +import template from "./input.component.html"; + +@Component({ + selector: 'sdc-input', + template: template, +}) +export class InputComponent extends ValidatableComponent implements OnInit { + + @Output('valueChange') public baseEmitter: EventEmitter = new EventEmitter(); + @Input() public label: string; + @Input() public value: any; + @Input() public name: string; + @Input() public classNames: string; + @Input() public disabled: boolean; + @Input() public type: string; + @Input() public placeHolder: string; + @Input() public required: boolean; + @Input() public minLength: number; + @Input() public maxLength: number; + @Input() public debounceTime: number; + @Input() public testId: string; + + protected control: FormControl; + + constructor() { + super(); + this.control = new FormControl('', []); + this.debounceTime = 0; + this.placeHolder = ''; + this.type = 'text'; + } + + ngOnInit() { + this.control.valueChanges. + debounceTime(this.debounceTime) + .subscribe((newValue: any) => { + this.baseEmitter.emit(this.value); + }); + } + + public getValue(): any { + return this.value; + } + + onKeyPress(value: string) { + this.valueChanged(this.value); + } + +} diff --git a/src/angular/form-elements/radios/radio-button.model.ts b/src/angular/form-elements/radios/radio-button.model.ts new file mode 100644 index 0000000..1ad4b3f --- /dev/null +++ b/src/angular/form-elements/radios/radio-button.model.ts @@ -0,0 +1,15 @@ +export interface IRadioButtonModel { + label: string; + disabled: boolean; + name: string; + value: string; +}; + +export interface IOptionGroup { + items: IRadioButtonModel[]; +}; + +export enum Direction { + vertical, + horizontal +} diff --git a/src/angular/form-elements/radios/radio-buttons-group.component.html.ts b/src/angular/form-elements/radios/radio-buttons-group.component.html.ts new file mode 100644 index 0000000..28a27af --- /dev/null +++ b/src/angular/form-elements/radios/radio-buttons-group.component.html.ts @@ -0,0 +1,20 @@ +export default ` + +
+ +
+`; diff --git a/src/angular/form-elements/radios/radio-buttons-group.component.spec.ts b/src/angular/form-elements/radios/radio-buttons-group.component.spec.ts new file mode 100644 index 0000000..273a701 --- /dev/null +++ b/src/angular/form-elements/radios/radio-buttons-group.component.spec.ts @@ -0,0 +1,52 @@ +import { TestBed, async } from '@angular/core/testing'; +import { RadioGroupComponent } from "./radio-buttons-group.component"; +import { FormsModule } from "@angular/forms"; +import { IRadioButtonModel } from "./radio-button.model"; +import { AnimationDirectivesModule } from "../../animations/animation-directives.module"; + + +describe("Radio Buttons unit-tests", ()=>{ + let component: RadioGroupComponent; + beforeEach(async(() => { + TestBed.configureTestingModule({ + declarations: [ + RadioGroupComponent + ], + imports:[ + FormsModule, + AnimationDirectivesModule + ] + }).compileComponents(); + + const fixture = TestBed.createComponent(RadioGroupComponent); + component = fixture.componentInstance; + component.disabled = false;//TODO constructor + component.options = { + items: [] + }; + })); + + it('Component Created', async(()=> { + expect(component).toBeDefined(); + })); + + it('Not possible to choose value which not exists', async(() =>{ + component.value = 'test'; + expect(component.value).not.toEqual('test'); + })); + + it('Normal flow', async(() =>{ + component.options.items = [ { + value: 'val1', + name: 'exp6', + label: 'Label of Radio1' + }, { + value: 'val2', + name: 'exp6', + label: 'Label of Radio2' + }]; + component.value = component.options.items[0].value; + expect(component.value).toEqual(component.options.items[0].value); + })); + +}); diff --git a/src/angular/form-elements/radios/radio-buttons-group.component.ts b/src/angular/form-elements/radios/radio-buttons-group.component.ts new file mode 100644 index 0000000..800d8b0 --- /dev/null +++ b/src/angular/form-elements/radios/radio-buttons-group.component.ts @@ -0,0 +1,52 @@ +import { Component, Input, Output, ViewEncapsulation, EventEmitter, HostBinding } from "@angular/core"; +import { Direction, IOptionGroup, IRadioButtonModel } from "./radio-button.model"; +import template from './radio-buttons-group.component.html'; + +@Component({ + selector: 'sdc-radio-group', + template: template, + encapsulation: ViewEncapsulation.None +}) +export class RadioGroupComponent { + + private _direction: Direction = Direction.vertical; + private _selectedValue: string; + + @HostBinding('class') classes = 'sdc-radio-group'; + + @Input() public legend: string; + @Input() public options: IOptionGroup; + @Input() public disabled: boolean; + + @Input() + get value(): string { + return this._selectedValue; + } + set value(value: string) { + if (this.isOptionExists(value)) { + this._selectedValue = value; + } + } + + @Output() public valueChange: EventEmitter = new EventEmitter(); + + @Input() + get direction(): string { + return Direction[this._direction]; + } + set direction(direction: string) { + this._direction = (direction === 'horizontal' ? Direction.horizontal : Direction.vertical); + } + + public onValueChanged(value): void { + this.valueChange.emit(value); + } + + private isOptionExists(value) { + const exist = this.options.items.find((item: IRadioButtonModel) => { + return item.value === value; + }); + return exist !== undefined; + } + +} diff --git a/src/angular/form-elements/validation/validatable.component.ts b/src/angular/form-elements/validation/validatable.component.ts new file mode 100644 index 0000000..4817dea --- /dev/null +++ b/src/angular/form-elements/validation/validatable.component.ts @@ -0,0 +1,25 @@ +import { Input, Component } from "@angular/core"; +import { ValidationComponent } from './validation.component'; +import { Subject } from 'rxjs/Subject'; +import { IValidatableComponent } from './validatable.interface'; + +export abstract class ValidatableComponent implements IValidatableComponent { + + // Each ValidatableComponent should handle the style in case of error, according to this boolean + public valid = true; + + // Each ValidatableComponent will notify when the value is changed. + public notifier: Subject; + + constructor() { + this.notifier = new Subject(); + } + + public abstract getValue(): any; + + // Each ValidatableComponent should call the valueChanged on value changed function. + protected valueChanged = (value: any): void => { + this.notifier.next(value); + } + +} diff --git a/src/angular/form-elements/validation/validatable.interface.ts b/src/angular/form-elements/validation/validatable.interface.ts new file mode 100644 index 0000000..6aceafe --- /dev/null +++ b/src/angular/form-elements/validation/validatable.interface.ts @@ -0,0 +1,5 @@ +export interface IValidatableComponent { + + getValue(): any; + +} diff --git a/src/angular/form-elements/validation/validation-group.component.html.ts b/src/angular/form-elements/validation/validation-group.component.html.ts new file mode 100644 index 0000000..dff591e --- /dev/null +++ b/src/angular/form-elements/validation/validation-group.component.html.ts @@ -0,0 +1,3 @@ +export default ` + +`; diff --git a/src/angular/form-elements/validation/validation-group.component.ts b/src/angular/form-elements/validation/validation-group.component.ts new file mode 100644 index 0000000..59ecf4c --- /dev/null +++ b/src/angular/form-elements/validation/validation-group.component.ts @@ -0,0 +1,47 @@ +import { Input, Component, ContentChildren, EventEmitter, Output, QueryList, SimpleChanges, HostBinding, AfterContentInit } from "@angular/core"; +import { AbstractControl, FormControl } from "@angular/forms"; +import { Subscribable } from "rxjs/Observable"; +import { AnonymousSubscription } from "rxjs/Subscription"; +import { IValidator } from './validators/validator.interface'; +import { ValidatorComponent } from './validators/base.validator.component'; +import { RegexValidatorComponent } from './validators/regex.validator.component'; +import { RequiredValidatorComponent } from './validators/required.validator.component'; +import { ValidatableComponent } from './validatable.component'; +import { ValidationComponent } from './validation.component'; +import { CustomValidatorComponent } from './validators/custom.validator.component'; +import template from "./validation.component.html"; + +@Component({ + selector: 'sdc-validation-group', + template +}) +export class ValidationGroupComponent implements AfterContentInit { + + @Input() public disabled: boolean; + @HostBinding('class') classes; + + @ContentChildren(ValidationComponent) public validationsComponents: QueryList; + + private supportedValidator: Array>; + + constructor() { + this.disabled = false; + this.classes = 'sdc-validation-group'; + } + + ngAfterContentInit(): void { + + } + + public validate(): boolean { + let validationResult = true; + // Iterate over all validationComponent inside the group and return boolean result true in case all validations passed. + this.validationsComponents.forEach((validationComponent) => { + if (validationComponent.validate()) { + validationResult = false; + } + }); + return validationResult; + } + +} diff --git a/src/angular/form-elements/validation/validation.component.html.ts b/src/angular/form-elements/validation/validation.component.html.ts new file mode 100644 index 0000000..0f11a23 --- /dev/null +++ b/src/angular/form-elements/validation/validation.component.html.ts @@ -0,0 +1,3 @@ +export default ` + +`; diff --git a/src/angular/form-elements/validation/validation.component.ts b/src/angular/form-elements/validation/validation.component.ts new file mode 100644 index 0000000..4abdd12 --- /dev/null +++ b/src/angular/form-elements/validation/validation.component.ts @@ -0,0 +1,79 @@ +import { Input, Component, ContentChildren, EventEmitter, Output, QueryList, SimpleChanges, HostBinding, AfterContentInit } from "@angular/core"; +import { AbstractControl, FormControl } from "@angular/forms"; +import { Subscribable } from "rxjs/Observable"; +import { AnonymousSubscription } from "rxjs/Subscription"; +import { IValidator } from './validators/validator.interface'; +import { ValidatorComponent } from './validators/base.validator.component'; +import { RegexValidatorComponent } from './validators/regex.validator.component'; +import { RequiredValidatorComponent } from './validators/required.validator.component'; +import { ValidatableComponent } from './validatable.component'; +import { CustomValidatorComponent } from './validators/custom.validator.component'; +import template from "./validation.component.html"; + +@Component({ + selector: 'sdc-validation', + template +}) +export class ValidationComponent implements AfterContentInit { + + @Input() public validateElement: ValidatableComponent; + @Input() public disabled: boolean; + @Output() public validityChanged: EventEmitter = new EventEmitter(); + @HostBinding('class') classes; + + // @ContentChildren does not recieve type any or IValidator or ValidatorComponent, so need to create @ContentChildren for each validator type. + @ContentChildren(RegexValidatorComponent) public regexValidator: QueryList; + @ContentChildren(RequiredValidatorComponent) public requireValidator: QueryList; + @ContentChildren(CustomValidatorComponent) public customValidator: QueryList; + + private supportedValidator: Array>; + + constructor() { + this.disabled = false; + this.classes = 'sdc-validation'; + } + + ngAfterContentInit(): void { + this.supportedValidator = [ + this.regexValidator, + this.requireValidator, + this.customValidator + ]; + + this.validateElement.notifier.subscribe( + (value) => { + const validationResult = this.validateOnChange(value); + this.validateElement.valid = validationResult; + }, + (error) => console.log('Validation subscribe error') + ); + } + + public validate = (): boolean => { + const value = this.validateElement.getValue(); + return this.validateOnChange(value); + } + + private validateOnChange(value: any): boolean { + if (this.disabled) { return true; } + + /** + * Iterate over all validators types (required, regex, etc...), and inside each iterate over + * all validators with same type, and return boolean result true in case all validations passed. + */ + const validationResult: boolean = this.supportedValidator.reduce((sum, validatorName) => { + const response: boolean = validatorName.reduce((_sum, validator) => { + return _sum && validator.validate(value); + }, true); + return sum && response; + }, true); + + if (this.validateElement.valid !== validationResult) { + this.validityChanged.emit(validationResult); + } + + return validationResult; + + } + +} diff --git a/src/angular/form-elements/validation/validation.module.ts b/src/angular/form-elements/validation/validation.module.ts new file mode 100644 index 0000000..4213f76 --- /dev/null +++ b/src/angular/form-elements/validation/validation.module.ts @@ -0,0 +1,35 @@ +import { NgModule } from "@angular/core"; +import { FormsModule, ReactiveFormsModule } from "@angular/forms"; +import { CommonModule } from "@angular/common"; +import { SvgIconModule } from './../../svg-icon/svg-icon.module'; +import { ValidationComponent } from './validation.component'; +import { ValidatorComponent } from './validators/base.validator.component'; +import { RequiredValidatorComponent } from './validators/required.validator.component'; +import { RegexValidatorComponent } from './validators/regex.validator.component'; +import { CustomValidatorComponent } from './validators/custom.validator.component'; +import { ValidationGroupComponent } from './validation-group.component'; + +@NgModule({ + imports: [ + FormsModule, + CommonModule, + ReactiveFormsModule, + SvgIconModule + ], + declarations: [ + ValidationComponent, + RegexValidatorComponent, + RequiredValidatorComponent, + CustomValidatorComponent, + ValidationGroupComponent + ], + exports: [ + ValidationComponent, + RegexValidatorComponent, + RequiredValidatorComponent, + CustomValidatorComponent, + ValidationGroupComponent + ] +}) +export class ValidationModule { +} diff --git a/src/angular/form-elements/validation/validators/base.validator.component.html.ts b/src/angular/form-elements/validation/validators/base.validator.component.html.ts new file mode 100644 index 0000000..aba8eed --- /dev/null +++ b/src/angular/form-elements/validation/validators/base.validator.component.html.ts @@ -0,0 +1,10 @@ +export default ` + + +`; diff --git a/src/angular/form-elements/validation/validators/base.validator.component.ts b/src/angular/form-elements/validation/validators/base.validator.component.ts new file mode 100644 index 0000000..3d751af --- /dev/null +++ b/src/angular/form-elements/validation/validators/base.validator.component.ts @@ -0,0 +1,25 @@ +import { Input, Component, ContentChildren, EventEmitter, Output, QueryList, SimpleChanges, HostBinding } from "@angular/core"; +import { IValidator } from './validator.interface'; +import template from "./base.validator.component.html"; + +@Component({ + selector: 'sdc-validator', + template: template +}) +export abstract class ValidatorComponent { + + @Input() public message: any; + @Input() public disabled: boolean; + @HostBinding('class') classes; + + protected isValid: boolean; + + constructor() { + this.disabled = false; + this.isValid = true; + this.classes = 'sdc-validator sdc-label__error'; + } + + public abstract validate(value: any): boolean; + +} diff --git a/src/angular/form-elements/validation/validators/custom.validator.component.ts b/src/angular/form-elements/validation/validators/custom.validator.component.ts new file mode 100644 index 0000000..eb09636 --- /dev/null +++ b/src/angular/form-elements/validation/validators/custom.validator.component.ts @@ -0,0 +1,23 @@ +import { Input, Component } from "@angular/core"; +import { ValidatorComponent } from "./base.validator.component"; +import { IValidator } from './validator.interface'; +import template from "./base.validator.component.html"; + +@Component({ + selector: 'sdc-custom-validator', + template: template +}) +export class CustomValidatorComponent extends ValidatorComponent implements IValidator { + + @Input() public callback: (...args) => boolean; + + constructor() { + super(); + } + + public validate(value: any): boolean { + this.isValid = this.callback(value); + return this.isValid; + } + +} diff --git a/src/angular/form-elements/validation/validators/regex.validator.component.ts b/src/angular/form-elements/validation/validators/regex.validator.component.ts new file mode 100644 index 0000000..5929016 --- /dev/null +++ b/src/angular/form-elements/validation/validators/regex.validator.component.ts @@ -0,0 +1,24 @@ +import { Input, Component } from "@angular/core"; +import { ValidatorComponent } from "./base.validator.component"; +import { IValidator } from './validator.interface'; +import template from "./base.validator.component.html"; + +@Component({ + selector: 'sdc-regex-validator', + template: template +}) +export class RegexValidatorComponent extends ValidatorComponent implements IValidator { + + @Input() public pattern: RegExp; + + constructor() { + super(); + } + + public validate(value: any): boolean { + const regexp = new RegExp(this.pattern); + this.isValid = regexp.test(value); + return this.isValid; + } + +} diff --git a/src/angular/form-elements/validation/validators/required.validator.component.ts b/src/angular/form-elements/validation/validators/required.validator.component.ts new file mode 100644 index 0000000..7eee932 --- /dev/null +++ b/src/angular/form-elements/validation/validators/required.validator.component.ts @@ -0,0 +1,25 @@ +import { Input, Component } from "@angular/core"; +import { ValidatorComponent } from "./base.validator.component"; +import { IValidator } from './validator.interface'; +import template from "./base.validator.component.html"; + +@Component({ + selector: 'sdc-required-validator', + template: template +}) +export class RequiredValidatorComponent extends ValidatorComponent implements IValidator { + + constructor() { + super(); + } + + public validate(value: any): boolean { + if (value) { + this.isValid = true; + } else { + this.isValid = false; + } + return this.isValid; + } + +} diff --git a/src/angular/form-elements/validation/validators/validator.interface.ts b/src/angular/form-elements/validation/validators/validator.interface.ts new file mode 100644 index 0000000..c0adc24 --- /dev/null +++ b/src/angular/form-elements/validation/validators/validator.interface.ts @@ -0,0 +1,3 @@ +export interface IValidator { + validate(value: any): void; +} diff --git a/src/angular/index.ts b/src/angular/index.ts new file mode 100644 index 0000000..e8a54bd --- /dev/null +++ b/src/angular/index.ts @@ -0,0 +1,68 @@ +import { NgModule } from "@angular/core"; +import { FormElementsModule } from "./form-elements/form-elements.module"; +import { ButtonsModule } from "./buttons/buttons.module"; +import { ModalModule } from "./modals/modal.module"; +import { NotificationModule } from "./notifications/notification.module"; +import { PopupMenuModule } from "./popup-menu/popup-menu.module"; +import { AnimationDirectivesModule } from "./animations/animation-directives.module"; +import { InfiniteScrollModule } from "./infinite-scroll/infinite-scroll.module"; +import { TileModule } from "./tiles/tile.module"; +import { ChecklistModule } from "./checklist/checklist.module"; +import { SvgIconModule } from "./svg-icon/svg-icon.module"; +import { AutoCompleteModule } from "./autocomplete/autocomplete.module"; +import { FilterBarModule } from "./filterbar/filter-bar.module"; +import { SearchBarModule } from "./searchbar/search-bar.module"; +import { TooltipModule } from "./tooltip/tooltip.module"; +import { TagCloudModule } from './tag-cloud/tag-cloud.module'; +import { TabsModule } from "./tabs/tabs.module"; +import { AccordionModule } from "./accordion/accordion.module"; + +@NgModule({ + imports: [ + AnimationDirectivesModule, + ModalModule, + NotificationModule, + FormElementsModule, + ButtonsModule, + PopupMenuModule, + InfiniteScrollModule, + TileModule, + ChecklistModule, + AutoCompleteModule, + FilterBarModule, + SearchBarModule, + TooltipModule, + SvgIconModule, + TagCloudModule, + TabsModule, + AccordionModule + ], + exports: [ + AnimationDirectivesModule, + ModalModule, + NotificationModule, + FormElementsModule, + ButtonsModule, + PopupMenuModule, + InfiniteScrollModule, + TileModule, + ChecklistModule, + AutoCompleteModule, + FilterBarModule, + SearchBarModule, + TooltipModule, + SvgIconModule, + TagCloudModule, + TabsModule, + AccordionModule + ] +}) +export class SdcUiComponentsModule { +} + +import * as SdcUiComponents from './components'; +import * as SdcUiCommon from './common/index'; + +export { SdcUiComponentsNg1Module } from './ng1.module'; +export { SdcUiComponents }; +export { SdcUiCommon }; diff --git a/src/angular/infinite-scroll/infinite-scroll.directive.ts b/src/angular/infinite-scroll/infinite-scroll.directive.ts new file mode 100644 index 0000000..a8ea9f4 --- /dev/null +++ b/src/angular/infinite-scroll/infinite-scroll.directive.ts @@ -0,0 +1,35 @@ +import { Directive, ElementRef, Output, EventEmitter, HostListener, Input } from "@angular/core"; + +@Directive({ + selector: '[infiniteScroll]' +}) +export class InfiniteScrollDirective { + @Input() public infiniteScrollDistance: number = 0; + @Output() public infiniteScroll: EventEmitter; + + private scrollWasHit: boolean = false; + + constructor(private elemRef: ElementRef) { + this.infiniteScroll = new EventEmitter(); + } + + @HostListener('scroll', ['$event']) + public onScroll(evt) { + const scrollContainerElem: HTMLElement = evt.target; + if (scrollContainerElem !== this.elemRef.nativeElement) { + return; + } + + if (scrollContainerElem.scrollTop + scrollContainerElem.clientHeight + this.infiniteScrollDistance >= + scrollContainerElem.scrollHeight) { + // hit only once when entering the distance area from bottom + // (avoid emitting the handler while scrolling in the bottom area) + if (!this.scrollWasHit) { + this.infiniteScroll.emit(); + this.scrollWasHit = true; + } + } else if (this.scrollWasHit) { + this.scrollWasHit = false; + } + } +} diff --git a/src/angular/infinite-scroll/infinite-scroll.module.ts b/src/angular/infinite-scroll/infinite-scroll.module.ts new file mode 100644 index 0000000..8b559ca --- /dev/null +++ b/src/angular/infinite-scroll/infinite-scroll.module.ts @@ -0,0 +1,13 @@ +import { NgModule } from "@angular/core"; +import { InfiniteScrollDirective } from "./infinite-scroll.directive"; + +@NgModule({ + declarations: [ + InfiniteScrollDirective + ], + exports: [ + InfiniteScrollDirective + ], +}) +export class InfiniteScrollModule { +} diff --git a/src/angular/modals/modal-button.component.ts b/src/angular/modals/modal-button.component.ts new file mode 100644 index 0000000..4fa5b7c --- /dev/null +++ b/src/angular/modals/modal-button.component.ts @@ -0,0 +1,29 @@ +import { Component, Input, HostListener } from "@angular/core"; +import { ButtonComponent } from "../buttons/button.component"; +import { ModalService } from "./modal.service"; +import template from "./../buttons/button.component.html"; + +@Component({ + selector: "sdc-modal-button", + template: template +}) +export class ModalButtonComponent extends ButtonComponent { + + @Input() public id?: string; + @Input() public callback: Function; + @Input() public closeModal: boolean; + @HostListener('click') invokeCallback = (): void => { + if (this.callback) { + this.callback(); + } + if (this.closeModal) { + this.modalService.closeModal(); + } + } + + constructor(private modalService: ModalService) { + super(); + this.closeModal = false; + } + +} diff --git a/src/angular/modals/modal-close-button.component.ts b/src/angular/modals/modal-close-button.component.ts new file mode 100644 index 0000000..e761019 --- /dev/null +++ b/src/angular/modals/modal-close-button.component.ts @@ -0,0 +1,34 @@ +import { Component, Input } from "@angular/core"; +import { ButtonComponent } from "../buttons/button.component"; +import { ModalService } from "./modal.service"; +import { RippleAnimationAction } from "../animations/ripple-click.animation.directive"; + +@Component({ + selector: "sdc-modal-close-button", + template: ` +
+ +
+ ` +}) +export class ModalCloseButtonComponent { + + @Input() testId: string; + @Input() disabled: boolean; + + public rippleAnimationAction: RippleAnimationAction = RippleAnimationAction.MOUSE_ENTER; + + constructor(private modalService: ModalService) { + } + + public closeModal = (): void => { + this.modalService.closeModal(); + } + +} diff --git a/src/angular/modals/modal.component.html.ts b/src/angular/modals/modal.component.html.ts new file mode 100644 index 0000000..90119ac --- /dev/null +++ b/src/angular/modals/modal.component.html.ts @@ -0,0 +1,38 @@ +export default ` +
+
+ +
+
+
+
+
+
+
+
+
+
{{ title }}
+ +
+
+
{{message}}
+
+
+ +
+
+ +`; diff --git a/src/angular/modals/modal.component.spec.ts b/src/angular/modals/modal.component.spec.ts new file mode 100644 index 0000000..372d59d --- /dev/null +++ b/src/angular/modals/modal.component.spec.ts @@ -0,0 +1,105 @@ +import { async, ComponentFixture, TestBed } from '@angular/core/testing'; +import { Component, Input, NgModule, ViewContainerRef, Inject, Injectable, Type, ApplicationRef, ComponentFactoryResolver, ComponentRef, EmbeddedViewRef, Injector } from '@angular/core'; +import { NO_ERRORS_SCHEMA } from '@angular/core/src/metadata/ng_module'; +import { ModalService } from './modal.service'; +import { CreateDynamicComponentService } from "../utils/create-dynamic-component.service"; +import { IModalConfig, ModalType, ModalSize } from "../../../src/angular/modals/models/modal-config"; +import { ModalInnerContent } from "../../../stories/ng2-component-lab/components/modal-inner-content-example.component"; + + +describe("Modal unit-tests", () => { + let testService: ModalService; + const testInputModal = { + size: 'xl', //'xl|l|md|sm|xsm' + title: 'Test_Title', + message: 'Test_Message', + modalVisible: true + }; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + providers:[ + ModalService, + { provide : CreateDynamicComponentService, useClass: CreateDynamicComponentServiceTest} + ], + declarations: [], + schemas:[NO_ERRORS_SCHEMA] + }) + testService = TestBed.get(ModalService); + })); + + it('Modal should be open test', () => { + let modalInstance = testService.openModal(testInputModal); + expect(modalInstance).toBeTruthy(); + }) + + it('Modal alert window test', () => { + let modalInstance = testService.openAlertModal('testAlert', 'testMessage'); + expect(modalInstance).toBeTruthy(); + }) + + it('Modal info window test', () => { + let modalInstance = testService.openErrorModal('testMessage', 'sampleTestId'); + expect(modalInstance).toBeTruthy(); + }) + + + it('Custom Modal should be open', () => { + let modalConfig:IModalConfig = { + size: ModalSize.medium, + title: 'Title', + type: ModalType.custom, + buttons: [{text:"Save & Close", closeModal:true}, + {text:"Save", callback:this.customModalOnSave, closeModal:false}, + {text:"Cancel", type: 'secondary', closeModal:true}] + }; + let modalInstance = testService.openCustomModal(modalConfig, ModalInnerContent, {name: "Sample Content"}); + expect(modalInstance).toBeTruthy(); + }) + + it('Shoul close window', () => { + let modalInstance = testService.openModal(testInputModal); + testService.closeModal(); + expect(modalInstance.instance.modalVisible).toBeFalsy(); + }) +}) + + +const testModalInstance = { + instance:{ + closeAnimationComplete:{ + subscribe:() => { + return true; + }, + }, + _createDynamicComponentService:{ + insertComponentDynamically:() => { + return true; + } + }, + modalVisible:true + }, + +}; + +@Component({ + selector: 'modal-test', + template: `
` +}) + + + +export class CreateDynamicComponentServiceTest { + modalVisble: true; + public createComponentDynamically = (modalInstance, customData) => { + return testModalInstance; + } + public insertComponentDynamically = () =>{ + return testModalInstance; + } + +} + + + + diff --git a/src/angular/modals/modal.component.ts b/src/angular/modals/modal.component.ts new file mode 100644 index 0000000..4f4d81f --- /dev/null +++ b/src/angular/modals/modal.component.ts @@ -0,0 +1,96 @@ +import { Component, Input, Output, ViewContainerRef, ViewChild, ComponentRef, trigger, state, animate, transition, style, EventEmitter, Renderer, ElementRef } from '@angular/core'; +import { ModalButtonComponent } from './modal-button.component'; +import { LowerCasePipe } from '@angular/common'; +import { ModalCloseButtonComponent } from './modal-close-button.component'; +import template from './modal.component.html'; + +@Component({ + selector: 'sdc-modal', + template: template, + animations: [ + trigger('toggleBackground', [ + transition('* => 1', [style({opacity: 0}), animate('.45s cubic-bezier(0.23, 1, 0.32, 1)')]), + transition('1 => *', [animate('.35s cubic-bezier(0.23, 1, 0.32, 1)', style({opacity: 0}))]) + ]), + trigger('toggleModal', [ + transition('* => 1', [style({opacity: 0, transform: 'translateY(-80px)'}), animate('.45s cubic-bezier(0.23, 1, 0.32, 1)')]), + transition('1 => *', [style({opacity: 1, transform: 'translateY(0px)'}), animate('.35s ease-in-out', style({opacity:0, transform: 'translateY(-80px)'}))]) + ]) + ] +}) + +export class ModalComponent { + + @Input() size: string; 'xl|l|md|sm|xsm'; + @Input() title: string; + @Input() message: string; + @Input() buttons: ModalButtonComponent[]; + @Input() type: string; 'info|error|alert|custom'; + @Input() testId: string; + @Output() closeAnimationComplete: EventEmitter = new EventEmitter(); + + @ViewChild('modalCloseButton') + set refCloseButton(_modalCloseButton: ModalCloseButtonComponent) { + this.modalCloseButton = _modalCloseButton; + } + + modalVisible: boolean; + // Allows for custom component as body instead of simple message. + // See ModalService.createActionModal for implementation details, and HttpService's catchError() for example. + @ViewChild('dynamicContentContainer', {read: ViewContainerRef}) dynamicContentContainer: ViewContainerRef; + innerModalContent: ComponentRef; + + public calculatedTestId: string; + public modalCloseButton: ModalCloseButtonComponent; + + constructor(private renderer: Renderer, + private lowerCasePipe: LowerCasePipe + ) { + this.modalVisible = true; + } + + getCalculatedTestId = (buttonText: string): string => { + // TODO: Replace this + if (this.testId) { + return this.testId + '-' + this.lowerCasePipe.transform(buttonText); + } + return null; + } + + public modalToggled = (toggleEvent: any) => { + if (!toggleEvent.toState) { + this.closeAnimationComplete.emit(); + } + } + + public getCloseButton = (): ModalCloseButtonComponent => { + return this.modalCloseButton; + } + + public getButtonById = (id: string): ModalButtonComponent => { + return this.buttons.find((button) => { + return button.id && button.id === id; + }); + } + + public getButtons = (): ModalButtonComponent[] => { + return this.buttons; + } + + public setButtons = (_buttons: ModalButtonComponent[]): void => { + this.buttons = _buttons; + } + + public getTitle = (): string => { + return this.title; + } + + public setTitle = (_title: string): void => { + this.title = _title; + } + + public hoverAnimation(evn: MouseEvent) { + this.renderer.setElementClass(evn.target as HTMLElement, 'sdc-ripple-click__animated', true); + // evn.taregt.classList.add('sdc-ripple-click__animated'); + } +} diff --git a/src/angular/modals/modal.module.ts b/src/angular/modals/modal.module.ts new file mode 100644 index 0000000..5697437 --- /dev/null +++ b/src/angular/modals/modal.module.ts @@ -0,0 +1,33 @@ +import { NgModule } from "@angular/core"; +import { ModalComponent } from "./modal.component"; +import { ModalService } from "./modal.service"; +import { CommonModule, LowerCasePipe } from "@angular/common"; +import { ButtonsModule } from "../buttons/buttons.module"; +import { AnimationDirectivesModule } from "../animations/animation-directives.module"; +import { CreateDynamicComponentService } from "../utils/create-dynamic-component.service"; +import { ModalButtonComponent } from "./modal-button.component"; +import { ModalCloseButtonComponent } from "./modal-close-button.component"; +import { SvgIconModule } from "../svg-icon/svg-icon.module"; + +@NgModule({ + declarations: [ + ModalComponent, + ModalButtonComponent, + ModalCloseButtonComponent + ], + imports: [ + CommonModule, + ButtonsModule, + AnimationDirectivesModule, + SvgIconModule + ], + entryComponents: [ + ModalComponent, + ModalCloseButtonComponent + ], + exports: [ModalButtonComponent], + providers: [CreateDynamicComponentService, ModalService, LowerCasePipe] +}) +export class ModalModule { + +} diff --git a/src/angular/modals/modal.service.ts b/src/angular/modals/modal.service.ts new file mode 100644 index 0000000..d80ad1f --- /dev/null +++ b/src/angular/modals/modal.service.ts @@ -0,0 +1,100 @@ +import { Injectable, Type, ComponentRef } from '@angular/core'; +import { ModalComponent } from "./modal.component"; +import { CreateDynamicComponentService } from "../utils/create-dynamic-component.service"; +import { IModalConfig, ModalType, ModalSize, IModalButtonComponent } from "./models/modal-config"; + +@Injectable() +export class ModalService { + + private currentModal: ComponentRef; + + constructor(private createDynamicComponentService: CreateDynamicComponentService) { + } + + /* Shortcut method to open an alert modal with title, message, and close button that simply closes the modal. */ + public openAlertModal(title: string, message: string, actionButtonText?: string, actionButtonCallback?: Function, testId?: string) { + const modalConfig = { + size: ModalSize.small, + title: title, + message: message, + testId: testId, + buttons: this.createButtons('secondary', actionButtonText, actionButtonCallback), + type: ModalType.alert + } as IModalConfig; + const modalInstance: ComponentRef = this.openModal(modalConfig); + this.currentModal = modalInstance; + return modalInstance; + } + + public openActionModal = (title: string, message: string, actionButtonText?: string, actionButtonCallback?: Function, testId?: string): ComponentRef => { + const modalConfig = { + size: ModalSize.small, + title: title, + message: message, + testId: testId, + type: ModalType.standard, + buttons: this.createButtons('primary', actionButtonText, actionButtonCallback) + } as IModalConfig; + const modalInstance: ComponentRef = this.openModal(modalConfig); + this.currentModal = modalInstance; + return modalInstance; + } + + public openErrorModal = (errorMessage?: string, testId?: string): ComponentRef => { + const modalConfig = { + size: ModalSize.small, + title: 'Error', + message: errorMessage, + testId: testId, + buttons: [{text: "OK", type: "alert", closeModal: true}], + type: ModalType.error + } as IModalConfig; + const modalInstance: ComponentRef = this.openModal(modalConfig); + this.currentModal = modalInstance; + return modalInstance; + } + + public openCustomModal = (modalConfig: IModalConfig, dynamicComponentType: Type, dynamicComponentInput?: any) => { + const modalInstance: ComponentRef = this.openModal(modalConfig); + this.createInnnerComponent(dynamicComponentType, dynamicComponentInput); + return modalInstance; + } + + public createInnnerComponent = (dynamicComponentType: Type, dynamicComponentInput?: any): void => { + this.currentModal.instance.innerModalContent = this.createDynamicComponentService.insertComponentDynamically(dynamicComponentType, dynamicComponentInput, this.currentModal.instance.dynamicContentContainer); + } + + public openModal = (customModalData: IModalConfig): ComponentRef => { + const modalInstance: ComponentRef = this.createDynamicComponentService.createComponentDynamically(ModalComponent, customModalData); + modalInstance.instance.closeAnimationComplete.subscribe(() => { + this.destroyModal(); + }); + this.currentModal = modalInstance; + return modalInstance; + } + + public getCurrentInstance = () => { + return this.currentModal.instance; + } + + public closeModal = (): void => { // triggers closeModal animation, which then triggers toggleModal.done and the subscription to destroyModal + this.currentModal.instance.modalVisible = false; + } + + private createButtons = (type: string, actionButtonText?: string, actionButtonCallback?: Function): Array => { + const buttons: Array = []; + if (actionButtonText && actionButtonCallback) { + buttons.push({text: actionButtonText, type: type, callback: actionButtonCallback, closeModal: true}); + buttons.push({text: 'Cancel', type: 'secondary', closeModal: true}); + } else { + buttons.push({text: 'Cancel', type: type, closeModal: true}); + } + + return buttons; + } + + private destroyModal = (): void => { + this.currentModal.destroy(); + } + +} diff --git a/src/angular/modals/models/modal-config.ts b/src/angular/modals/models/modal-config.ts new file mode 100644 index 0000000..635942b --- /dev/null +++ b/src/angular/modals/models/modal-config.ts @@ -0,0 +1,44 @@ +import { Placement } from "../../common/enums"; + +export interface IModalConfig { + size?: string; // xl|l|md|sm|xsm + title?: string; + message?: string; + buttons?: IModalButtonComponent[]; + testId?: string; + type?: string; // 'info|error|alert'; +} + +export interface IButtonComponent { + text: string; + disabled?: boolean; + type?: string; + testId?: string; + preventDoubleClick?: boolean; + icon_name?: string; + icon_position?: string; + show_spinner?: boolean; + spinner_position?: Placement; + size?: string; +} + +export interface IModalButtonComponent extends IButtonComponent{ + id?: string; + callback?: Function; + closeModal?: boolean; +} + +export enum ModalType { + alert = "alert", + error = "error", + standard = "info", + custom = "custom" +} + +export enum ModalSize { + xlarge = "xl", + large = "l", + medium = "md", + small = "sm", + xsmall = "xsm" +} diff --git a/src/angular/ng1.module.ts b/src/angular/ng1.module.ts new file mode 100644 index 0000000..6f636f4 --- /dev/null +++ b/src/angular/ng1.module.ts @@ -0,0 +1,135 @@ +import { SdcUiComponentsModule } from './index'; +import { downgradeComponent, downgradeInjectable } from "@angular/upgrade/static"; +import * as Components from './components'; +declare const angular: any; + +let SdcUiComponentsNg1Module = null; + +if (typeof angular !== "undefined") { + + SdcUiComponentsNg1Module = angular.module('SdcUiComponentsNg1', []); + + // // Form Elements + SdcUiComponentsNg1Module.directive('sdcInput', downgradeComponent({ + component: Components.InputComponent, + inputs: ['label', 'value', 'pattern', 'disabled', 'placeHolder', 'required', 'minLength', 'maxLength', 'debounceTime'], + outputs: ['valueChange'] + })); + SdcUiComponentsNg1Module.directive('sdcDropdown', downgradeComponent({ + component: Components.DropDownComponent, + inputs: ['label', 'options', 'disabled', 'placeHolder', 'required', 'validate', 'headless', 'maxHeight', 'selectedOption'], + outputs: ['changeEmitter'] + })); + SdcUiComponentsNg1Module.directive('sdcCheckbox', downgradeComponent({ + component: Components.CheckboxComponent, + inputs: ['label', 'checked', 'disabled'], + outputs: ['checkedChange'] + })); + SdcUiComponentsNg1Module.directive('sdcRadioGroup', downgradeComponent({ + component: Components.RadioGroupComponent, + inputs: ['legend', 'options', 'disabled', 'value', 'direction'], + outputs: ['valueChange'] + })); + + // Buttons + SdcUiComponentsNg1Module.directive('sdcButton', downgradeComponent({ + component: Components.ButtonComponent, + inputs: ['text', 'disabled', 'type', 'size', 'preventDoubleClick', 'icon_name', 'icon_positon'] + })); + + // Modals + SdcUiComponentsNg1Module.service('SdcModalService', downgradeInjectable(Components.ModalService)); + SdcUiComponentsNg1Module.directive('sdcModal', downgradeComponent({ + component: Components.ModalComponent, + inputs: ['size', 'title', 'message', 'buttons', 'type'], + outputs: ['closeAnimationComplete'] + })); + SdcUiComponentsNg1Module.directive('sdcModalButton', downgradeComponent({ + component: Components.ModalButtonComponent, + inputs: ['callback', 'closeModal'] + })); + + // Notifications + SdcUiComponentsNg1Module.service('SdcNotificationService', downgradeInjectable(Components.NotificationsService)); + SdcUiComponentsNg1Module.directive('sdcNotificationContainer', downgradeComponent({ + component: Components.NotificationContainerComponent + })); + SdcUiComponentsNg1Module.directive('sdcNotification', downgradeComponent({ + component: Components.NotificationComponent, + inputs: ['notificationSetting'], + outputs: ['destroyComponent'] + })); + + // Popup Menu + SdcUiComponentsNg1Module.directive('popupMenuList', downgradeComponent({ + component: Components.PopupMenuListComponent, + inputs: ['open', 'position', 'className', 'relative'], + outputs: ['openChange', 'positionChange'] + })); + SdcUiComponentsNg1Module.directive('popupMenuItem', downgradeComponent({ + component: Components.PopupMenuItemComponent, + inputs: ['className', 'type'], + outputs: ['action'] + })); + + // Tiles + SdcUiComponentsNg1Module.directive('sdcTile', downgradeComponent({ + component: Components.TileComponent + })); + SdcUiComponentsNg1Module.directive('sdcTileHeader', downgradeComponent({ + component: Components.TileHeaderComponent + })); + SdcUiComponentsNg1Module.directive('sdcTileContent', downgradeComponent({ + component: Components.TileContentComponent + })); + SdcUiComponentsNg1Module.directive('sdcTileFooter', downgradeComponent({ + component: Components.TileFooterComponent + })); + + // Check List + SdcUiComponentsNg1Module.directive('sdcChecklist', downgradeComponent({ + component: Components.ChecklistComponent, + inputs: ['checklistModel'], + outputs: ['checkedChange'] + })); + + // Tag Cloud + SdcUiComponentsNg1Module.directive('sdcTagCloud', downgradeComponent({ + component: Components.TagCloudComponent, + inputs: ['list', 'isViewOnly', 'isUniqueList', 'uniqueErrorMessage', 'label', 'placeholder'], + outputs: ['listChanged'] + })); + SdcUiComponentsNg1Module.directive('sdcTagItem', downgradeComponent({ + component: Components.TagItemComponent, + inputs: ['text', 'isViewOnly', 'index'], + outputs: ['clickOnDelete'] + })); + + // Tabs + SdcUiComponentsNg1Module.directive('sdcTabs', downgradeComponent({ + component: Components.TabsComponent + })); + SdcUiComponentsNg1Module.directive('sdcTab', downgradeComponent({ + component: Components.TabComponent, + inputs: ['title', 'active'] + })); + + // Svg Icons + SdcUiComponentsNg1Module.directive('svgIcon', downgradeComponent({ + component: Components.SvgIconComponent, + inputs: ['name', 'mode', 'size', 'disabled', 'clickable', 'className'] + })); + SdcUiComponentsNg1Module.directive('svgIconLabel', downgradeComponent({ + component: Components.SvgIconLabelComponent, + inputs: ['name', 'mode', 'size', 'disabled', 'clickable', 'className', 'label', 'labelPlacement', 'labelClassName'] + })); + + // Accordion + SdcUiComponentsNg1Module.directive('sdcAccordion', downgradeComponent({ + component: Components.AccordionComponent, + inputs: ['arrow-direction', 'css-class', 'title', 'open'], + outputs: ['accordionChanged'] + })); +} + +export {SdcUiComponentsNg1Module}; diff --git a/src/angular/notifications/container/notifcontainer.component.html.ts b/src/angular/notifications/container/notifcontainer.component.html.ts new file mode 100644 index 0000000..df08bb4 --- /dev/null +++ b/src/angular/notifications/container/notifcontainer.component.html.ts @@ -0,0 +1,6 @@ +export default ` +
+ + +
+`; diff --git a/src/angular/notifications/container/notifcontainer.component.ts b/src/angular/notifications/container/notifcontainer.component.ts new file mode 100644 index 0000000..a922dc1 --- /dev/null +++ b/src/angular/notifications/container/notifcontainer.component.ts @@ -0,0 +1,31 @@ +import { Component, Input, Output, EventEmitter, OnInit } from "@angular/core"; +import { CommonModule } from "@angular/common"; +import { NotificationSettings } from "../utilities/notification.config"; +import { NotificationsService } from "../services/notifications.service"; +import template from "./notifcontainer.component.html"; + +@Component({ + selector: "sdc-notification-container", + template: template +}) +export class NotificationContainerComponent implements OnInit { + notifications: NotificationSettings[] = []; + + constructor(private notify: NotificationsService) { + } + + public ngOnInit(){ + this.notify.subscribe( (notif : NotificationSettings) => { + this.notifications.push(notif); + }); + } + + + private onDestroyed = (event : any):void =>{ + let index: number = this.notifications.indexOf(event); + if (index !== -1) { + this.notifications.splice(index, 1); + } + } + +} diff --git a/src/angular/notifications/notification-inner-content-example.component.ts b/src/angular/notifications/notification-inner-content-example.component.ts new file mode 100644 index 0000000..552f7b0 --- /dev/null +++ b/src/angular/notifications/notification-inner-content-example.component.ts @@ -0,0 +1,21 @@ +import { Component, Input } from "@angular/core"; + +@Component({ + selector: "innernotif-content", + template: ` +
+

Custom Notification

+
+ {{notifyTitle}} +
+
+ {{notifyText}} +
+
+` +}) +export class InnerNotifContent { + + @Input() notifyTitle:string; + @Input() notifyText:string; +} diff --git a/src/angular/notifications/notification.module.ts b/src/angular/notifications/notification.module.ts new file mode 100644 index 0000000..5891391 --- /dev/null +++ b/src/angular/notifications/notification.module.ts @@ -0,0 +1,29 @@ +import { NgModule } from "@angular/core"; +import { CommonModule } from "@angular/common"; +import { NotificationComponent } from "./notification/notification.component"; +import { NotificationContainerComponent } from "./container/notifcontainer.component"; +import { NotificationsService } from "./services/notifications.service"; +import { CreateDynamicComponentService } from "../utils/create-dynamic-component.service"; + + +@NgModule({ + declarations: [ + NotificationComponent, + NotificationContainerComponent, + ], + exports: [ + NotificationComponent, + NotificationContainerComponent, + ], + entryComponents: [ + NotificationComponent, + NotificationContainerComponent, + ], + imports: [ + CommonModule + ], + providers: [NotificationsService, CreateDynamicComponentService] +}) +export class NotificationModule { + +} diff --git a/src/angular/notifications/notification/notification.component.html.ts b/src/angular/notifications/notification/notification.component.html.ts new file mode 100644 index 0000000..450972e --- /dev/null +++ b/src/angular/notifications/notification/notification.component.html.ts @@ -0,0 +1,19 @@ +export default ` +
+
+
+
+
+
+
+ {{notificationSetting.notifyTitle}} +
+
+ {{notificationSetting.notifyText}} +
+
+
+
+
+
+`; diff --git a/src/angular/notifications/notification/notification.component.ts b/src/angular/notifications/notification/notification.component.ts new file mode 100644 index 0000000..476853c --- /dev/null +++ b/src/angular/notifications/notification/notification.component.ts @@ -0,0 +1,42 @@ +import { Component, Input, Output, EventEmitter, OnInit, ViewContainerRef, ViewChild } from "@angular/core"; +import { NotificationSettings } from "../utilities/notification.config"; +import { CreateDynamicComponentService } from "../../utils/create-dynamic-component.service"; +import template from "./notification.component.html"; + +@Component({ + selector: 'sdc-notification', + template: template +}) + +export class NotificationComponent implements OnInit { + + @Input() notificationSetting:NotificationSettings; + @Output() destroyComponent = new EventEmitter(); + @ViewChild("dynamicContentContainer", {read: ViewContainerRef}) contentContainer:ViewContainerRef; + private fade: boolean = false; + + constructor(private createDynamicComponentService: CreateDynamicComponentService) { + } + + public ngOnInit() { + if(this.notificationSetting.hasCustomContent){ + this.createDynamicComponentService.insertComponentDynamically(this.notificationSetting.innerComponentType, this.notificationSetting.innerComponentOptions, this.contentContainer); + } + + if(!this.notificationSetting.sticky){ + setTimeout(() => this.fadeOut(), this.notificationSetting.duration); + } + } + + private fadeOut = ():void => { + this.fade = true; + } + + private destroyMe() { + /*Only destroy on fade out, not on entry animation */ + if(this.fade){ + this.destroyComponent.emit(this.notificationSetting); + } + } + +} diff --git a/src/angular/notifications/services/notifications.service.ts b/src/angular/notifications/services/notifications.service.ts new file mode 100644 index 0000000..28a645c --- /dev/null +++ b/src/angular/notifications/services/notifications.service.ts @@ -0,0 +1,41 @@ +import { Injectable } from '@angular/core'; +import { NotificationSettings } from '../utilities/notification.config' +import { Subject } from 'rxjs/Subject'; +import { Subscription } from 'rxjs/Subscription'; + + +@Injectable() +export class NotificationsService { + + notifs : NotificationSettings[] = []; + + notifQueue : Subject = new Subject(); + + constructor() {} + + public push(notif : NotificationSettings):void{ + + if( this.notifQueue.observers.length > 0 ) { + this.notifQueue.next(notif); + } else { + this.notifs.push(notif); + } + } + + + + public getNotifications() : NotificationSettings[] { + return this.notifs; + } + + + + public subscribe(observer): Subscription { + let s:Subscription = this.notifQueue.subscribe(observer); + this.notifs.forEach(notif => this.notifQueue.next(notif)); + this.notifs = []; + return s; + } + + +} diff --git a/src/angular/notifications/utilities/notification.config.ts b/src/angular/notifications/utilities/notification.config.ts new file mode 100644 index 0000000..f469b7d --- /dev/null +++ b/src/angular/notifications/utilities/notification.config.ts @@ -0,0 +1,30 @@ +import { Type, ComponentRef } from '@angular/core'; + +export type NotificationType = + "info" | "warn" | "error" | "success"; + +export class NotificationSettings { + + public type: NotificationType; + public notifyText: string; + public notifyTitle: string; + public sticky: boolean; + public hasCustomContent :boolean; + public duration:number; + public innerComponentType: Type; + public innerComponentOptions : any; + + constructor(type: NotificationType, notifyText: string, notifyTitle: string, duration: number = 10000, sticky: boolean = false, hasCustomContent:boolean = false, innerComponentType?:Type, innerComponentOptions? :any) { + + this.type = type; + this.notifyText = notifyText; + this.notifyTitle = notifyTitle; + this.duration = duration; + this.sticky = sticky; + this.hasCustomContent = hasCustomContent; + this.innerComponentType = innerComponentType; + this.innerComponentOptions = innerComponentOptions; + } + + +} diff --git a/src/angular/popup-menu/popup-menu-item.component.spec.ts b/src/angular/popup-menu/popup-menu-item.component.spec.ts new file mode 100644 index 0000000..25b2694 --- /dev/null +++ b/src/angular/popup-menu/popup-menu-item.component.spec.ts @@ -0,0 +1,25 @@ +import { Component, Input, Output, ContentChildren, SimpleChanges, QueryList, EventEmitter, OnChanges, AfterContentInit } from '@angular/core'; +import { FormsModule } from "@angular/forms"; +import { async, ComponentFixture, TestBed } from '@angular/core/testing'; +import { PopupMenuItemComponent } from './popup-menu-item.component'; +import { PopupMenuListComponent } from './popup-menu-list.component'; + +describe('Popup Menu', () => { + let component: PopupMenuListComponent; + let fixture: ComponentFixture; + beforeEach(async(() => { + TestBed.configureTestingModule({ + declarations: [ PopupMenuListComponent ], + }).compileComponents(); + fixture = TestBed.createComponent(PopupMenuListComponent); + component = fixture.componentInstance; + })); + + it('Popup menu component should be created', () => { + expect(component).toBeTruthy(); + }); + + it('Set Position to Popup Menu', () => { + expect(component.position).toEqual({ x: 0, y: 0 }) + }); +}) diff --git a/src/angular/popup-menu/popup-menu-item.component.ts b/src/angular/popup-menu/popup-menu-item.component.ts new file mode 100644 index 0000000..fb5a71d --- /dev/null +++ b/src/angular/popup-menu/popup-menu-item.component.ts @@ -0,0 +1,34 @@ +import { Component, Input, Output, EventEmitter, OnChanges, SimpleChanges } from '@angular/core'; +import { PopupMenuListComponent } from "./popup-menu-list.component"; + +@Component({ + selector: 'popup-menu-item', + template: + `
  • + +
  • ` +}) +export class PopupMenuItemComponent { + @Input() public className: string; + @Input() public type: undefined|'disabled'|'selected'|'separator'; + @Output() public action: EventEmitter = new EventEmitter(); + + public parentMenu: PopupMenuListComponent; + public index: number = 0; + + public performAction(evt) { + evt.stopPropagation(); + + if (['disabled', 'separator'].indexOf(this.type) !== -1) { + return; + } + + if (this.parentMenu instanceof PopupMenuListComponent) { + this.parentMenu.open = false; + } + + if (this.action) { + this.action.emit(); + } + } +} diff --git a/src/angular/popup-menu/popup-menu-list.component.ts b/src/angular/popup-menu/popup-menu-list.component.ts new file mode 100644 index 0000000..6a20423 --- /dev/null +++ b/src/angular/popup-menu/popup-menu-list.component.ts @@ -0,0 +1,65 @@ +import { Component, Input, Output, ContentChildren, SimpleChanges, QueryList, EventEmitter, OnChanges, AfterContentInit } from '@angular/core'; +import { PopupMenuItemComponent } from "./popup-menu-item.component"; + +export interface IPoint { + x: number; + y: number; +} + +@Component({ + selector: 'popup-menu-list', + template: + `
      + +
    ` +}) +export class PopupMenuListComponent implements AfterContentInit { + @Input() + public get open(): boolean { + return this._open; + } + public set open(isOpen: boolean) { + isOpen = isOpen !== undefined ? isOpen : false; + if (this._open !== isOpen) { + this._open = isOpen; + this.openChange.emit(this._open); + } + } + @Input() + public get position(): IPoint { + return this._position; + } + public set position(position: IPoint) { + position = position !== undefined ? position : {x: 0, y: 0}; + if (this._position.x !== position.x || this._position.y !== position.y) { + this._position = position; + this.positionChange.emit(this._position); + } + } + @Input() public className: string; + @Input() public relative: boolean = false; + @Output() public openChange: EventEmitter = new EventEmitter(); + @Output() public positionChange: EventEmitter = new EventEmitter(); + + @ContentChildren(PopupMenuItemComponent) private menuItems: QueryList; + + private _open: boolean = false; + private _position: IPoint = {x: 0, y: 0}; + + public ngAfterContentInit() { + this._updateMenuItemsList(this.menuItems); + this.menuItems.changes.subscribe(this._updateMenuItemsList); + } + + private _updateMenuItemsList(menuItemsList: QueryList) { + menuItemsList.forEach((c, idx) => { + c.parentMenu = this; + c.index = idx; + }); + } +} diff --git a/src/angular/popup-menu/popup-menu.module.ts b/src/angular/popup-menu/popup-menu.module.ts new file mode 100644 index 0000000..3a58b91 --- /dev/null +++ b/src/angular/popup-menu/popup-menu.module.ts @@ -0,0 +1,21 @@ +import { NgModule } from "@angular/core"; +import { PopupMenuListComponent } from "./popup-menu-list.component"; +import { PopupMenuItemComponent } from "./popup-menu-item.component"; +import { CommonModule } from "@angular/common"; + + +@NgModule({ + declarations: [ + PopupMenuListComponent, + PopupMenuItemComponent + ], + imports: [ + CommonModule + ], + exports: [ + PopupMenuListComponent, + PopupMenuItemComponent + ], +}) +export class PopupMenuModule { +} diff --git a/src/angular/searchbar/search-bar.component.html.ts b/src/angular/searchbar/search-bar.component.html.ts new file mode 100644 index 0000000..79153f4 --- /dev/null +++ b/src/angular/searchbar/search-bar.component.html.ts @@ -0,0 +1,19 @@ +export default ` +
    + + + + + + + + + + + +
    +`; diff --git a/src/angular/searchbar/search-bar.component.ts b/src/angular/searchbar/search-bar.component.ts new file mode 100644 index 0000000..7f508d7 --- /dev/null +++ b/src/angular/searchbar/search-bar.component.ts @@ -0,0 +1,19 @@ +import { Component, Input, Output, EventEmitter, HostBinding } from '@angular/core'; +import template from "./search-bar.component.html"; + +@Component({ + selector: 'sdc-search-bar', + template: template +}) +export class SearchBarComponent { + + @HostBinding('class') classes = 'sdc-search-bar'; + @Input() public placeholder: string; + @Input() public label: string; + @Input() public searchQuery: string; + @Output() public searchQueryClick: EventEmitter = new EventEmitter(); + + private searchButtonClick = (): void => { + this.searchQueryClick.emit(this.searchQuery); + } +} diff --git a/src/angular/searchbar/search-bar.module.ts b/src/angular/searchbar/search-bar.module.ts new file mode 100644 index 0000000..27750f3 --- /dev/null +++ b/src/angular/searchbar/search-bar.module.ts @@ -0,0 +1,17 @@ +import { NgModule } from "@angular/core"; +import { SearchBarComponent } from "./search-bar.component"; +import { CommonModule } from "@angular/common"; +import { FormElementsModule } from "../form-elements/form-elements.module"; + +@NgModule({ + declarations: [ + SearchBarComponent + ], + imports: [CommonModule, + FormElementsModule], + exports: [ + SearchBarComponent + ], +}) +export class SearchBarModule { +} diff --git a/src/angular/svg-icon/svg-icon-label.component.html.ts b/src/angular/svg-icon/svg-icon-label.component.html.ts new file mode 100644 index 0000000..558b7c4 --- /dev/null +++ b/src/angular/svg-icon/svg-icon-label.component.html.ts @@ -0,0 +1,6 @@ +export default ` +
    + + {{ label }} +
    +`; diff --git a/src/angular/svg-icon/svg-icon-label.component.ts b/src/angular/svg-icon/svg-icon-label.component.ts new file mode 100644 index 0000000..5a00c3d --- /dev/null +++ b/src/angular/svg-icon/svg-icon-label.component.ts @@ -0,0 +1,26 @@ +import { Component, Input } from "@angular/core"; +import { SvgIconComponent } from './svg-icon.component'; +import { Mode, Size, Placement } from "../common/enums"; +import { DomSanitizer, SafeHtml } from "@angular/platform-browser"; +import template from './svg-icon-label.component.html'; + +@Component({ + selector: 'svg-icon-label', + template: template, + styles: [` + :host { + display: inline-flex; + } + `] +}) +export class SvgIconLabelComponent extends SvgIconComponent { + + @Input() public label: string; + @Input() public labelPlacement: Placement; + @Input() public labelClassName: string; + + constructor(protected domSanitizer: DomSanitizer) { + super(domSanitizer); + this.labelPlacement = Placement.left; + } +} diff --git a/src/angular/svg-icon/svg-icon.component.html.ts b/src/angular/svg-icon/svg-icon.component.html.ts new file mode 100644 index 0000000..1baedbd --- /dev/null +++ b/src/angular/svg-icon/svg-icon.component.html.ts @@ -0,0 +1,3 @@ +export default ` +
    +`; diff --git a/src/angular/svg-icon/svg-icon.component.ts b/src/angular/svg-icon/svg-icon.component.ts new file mode 100644 index 0000000..d53981d --- /dev/null +++ b/src/angular/svg-icon/svg-icon.component.ts @@ -0,0 +1,77 @@ +import { Component, Input, OnChanges, SimpleChanges, HostBinding } from "@angular/core"; +import { DomSanitizer, SafeHtml } from "@angular/platform-browser"; +import { Mode, Size } from "../common/enums"; +import iconsMap from '../../common/icons-map'; +import template from './svg-icon.component.html'; + +@Component({ + selector: 'svg-icon', + template: template, + styles: [` + :host { + display: inline-flex; + } + `] +}) +export class SvgIconComponent implements OnChanges { + + @Input() public name: string; + @Input() public mode: Mode; + @Input() public size: Size; + @Input() public disabled: boolean; + @Input() public clickable: boolean; + @Input() public className: any; + + public svgIconContent: string; + public svgIconContentSafeHtml: SafeHtml; + public svgIconCustomClassName: string; + private classes: string; + + constructor(protected domSanitizer: DomSanitizer) { + this.size = Size.medium; + this.disabled = false; + } + + static get Icons(): {[key: string]: string} { + return iconsMap; + } + + public ngOnChanges(changes: SimpleChanges) { + if (changes.name) { + this.updateSvgIconByName(); + this.buildClasses(); + } + } + + protected updateSvgIconByName() { + this.svgIconContent = SvgIconComponent.Icons[this.name] || null; + if (this.svgIconContent) { + this.svgIconContentSafeHtml = this.domSanitizer.bypassSecurityTrustHtml(this.svgIconContent); + this.svgIconCustomClassName = '__' + this.name.replace(/\s+/g, '_'); + } else { + this.svgIconContentSafeHtml = null; + this.svgIconCustomClassName = 'missing'; + } + } + + private buildClasses = (): void => { + const _classes = []; + _classes.push('svg-icon'); + if (this.mode) { + _classes.push('mode-' + this.mode); + } + if (this.size) { + _classes.push('size-' + this.size); + } + if (this.clickable) { + _classes.push('clickable'); + } + if (this.svgIconCustomClassName) { + _classes.push(this.svgIconCustomClassName); + } + if (this.className) { + _classes.push(this.className); + } + this.classes = _classes.join(" "); + } +} diff --git a/src/angular/svg-icon/svg-icon.module.ts b/src/angular/svg-icon/svg-icon.module.ts new file mode 100644 index 0000000..87a0d86 --- /dev/null +++ b/src/angular/svg-icon/svg-icon.module.ts @@ -0,0 +1,21 @@ +import { NgModule } from "@angular/core"; +import { CommonModule } from "@angular/common"; +import { SvgIconComponent } from "./svg-icon.component"; +import { SvgIconLabelComponent } from "./svg-icon-label.component"; + +@NgModule({ + declarations: [ + SvgIconComponent, + SvgIconLabelComponent + ], + imports: [ + CommonModule + ], + exports: [ + SvgIconComponent, + SvgIconLabelComponent + ], +}) + +export class SvgIconModule { +} diff --git a/src/angular/tabs/children/tab.component.html.ts b/src/angular/tabs/children/tab.component.html.ts new file mode 100644 index 0000000..36ff413 --- /dev/null +++ b/src/angular/tabs/children/tab.component.html.ts @@ -0,0 +1,5 @@ +export default ` +
    + +
    +`; diff --git a/src/angular/tabs/children/tab.component.ts b/src/angular/tabs/children/tab.component.ts new file mode 100644 index 0000000..3b96e87 --- /dev/null +++ b/src/angular/tabs/children/tab.component.ts @@ -0,0 +1,16 @@ +import { Component, Input } from '@angular/core'; +import { Mode } from './../../common/enums'; +import template from "./tab.component.html"; + +@Component({ + selector: 'sdc-tab', + template: template +}) +export class TabComponent { + @Input() public title: string; + @Input() public titleIcon: string; + @Input() public active = false; + + public titleIconMode = Mode.secondary; + +} diff --git a/src/angular/tabs/tabs.component.html.ts b/src/angular/tabs/tabs.component.html.ts new file mode 100644 index 0000000..2333b86 --- /dev/null +++ b/src/angular/tabs/tabs.component.html.ts @@ -0,0 +1,14 @@ +export default ` +
      + +
    + +`; diff --git a/src/angular/tabs/tabs.component.ts b/src/angular/tabs/tabs.component.ts new file mode 100644 index 0000000..595f304 --- /dev/null +++ b/src/angular/tabs/tabs.component.ts @@ -0,0 +1,41 @@ +import { Component, Input, AfterContentInit, ContentChildren, QueryList, HostBinding } from '@angular/core'; +import { TabComponent } from './children/tab.component'; +import { SvgIconComponent } from "./../../../src/angular/svg-icon/svg-icon.component"; +import { Mode, Placement, Size } from './../common/enums'; +import template from "./tabs.component.html"; + +@Component({ + selector: 'sdc-tabs', + template: template +}) + +export class TabsComponent implements AfterContentInit { + + @HostBinding('class') classes = 'sdc-tabs sdc-tabs-header'; + @ContentChildren(TabComponent) private tabs: QueryList; + + public _size = Size.medium; + + public selectTab(tab: TabComponent) { + // deactivate all tabs + this.tabs.toArray().forEach((_tab: TabComponent) => { + _tab.active = false; + _tab.titleIconMode = Mode.secondary; + }); + + // activate the tab the user has clicked on. + tab.active = true; + tab.titleIconMode = Mode.primary; + } + + public ngAfterContentInit() { + // get all active tabs + const activeTabs = this.tabs.filter((tab) => tab.active); + + // if there is no active tab set, activate the first + if (activeTabs.length === 0) { + this.selectTab(this.tabs.first); + } + } + + } diff --git a/src/angular/tabs/tabs.module.ts b/src/angular/tabs/tabs.module.ts new file mode 100644 index 0000000..107942d --- /dev/null +++ b/src/angular/tabs/tabs.module.ts @@ -0,0 +1,23 @@ +import { NgModule } from "@angular/core"; +import { CommonModule } from "@angular/common"; +import { FormElementsModule } from "../form-elements/form-elements.module"; +import { TabsComponent } from "./tabs.component"; +import { TabComponent } from './children/tab.component'; +import { SvgIconModule } from './../svg-icon/svg-icon.module'; + +@NgModule({ + declarations: [ + TabsComponent, + TabComponent + ], + imports: [ + CommonModule, + SvgIconModule + ], + exports: [ + TabsComponent, + TabComponent + ] +}) +export class TabsModule { +} diff --git a/src/angular/tag-cloud/tag-cloud.component.html.ts b/src/angular/tag-cloud/tag-cloud.component.html.ts new file mode 100644 index 0000000..2ff4e8a --- /dev/null +++ b/src/angular/tag-cloud/tag-cloud.component.html.ts @@ -0,0 +1,30 @@ +export default ` +
    + +
    + + + + + + + + + + +
    +
    +
    + +
    +
    {{uniqueErrorMessage}}
    +`; diff --git a/src/angular/tag-cloud/tag-cloud.component.ts b/src/angular/tag-cloud/tag-cloud.component.ts new file mode 100644 index 0000000..1635b8d --- /dev/null +++ b/src/angular/tag-cloud/tag-cloud.component.ts @@ -0,0 +1,46 @@ +import { Component, EventEmitter, Input, Output } from "@angular/core"; +import template from "./tag-cloud.component.html"; + +@Component({ + selector: 'sdc-tag-cloud', + template: template, +}) +export class TagCloudComponent { + @Input() public list: string[]; + @Input() public isViewOnly: boolean|number[]; // get a boolean parameter or array of specific items indexes. + @Input() public isUniqueList: boolean; + @Input() public uniqueErrorMessage: string = "Unique error"; + @Input() public label: string; + @Input() public placeholder: string; + @Output() public listChanged: EventEmitter = new EventEmitter(); + private newTagItem: string; + private uniqueError: boolean; + + private onKeyup = (e): void => { + if (e.keyCode === 13) { + this.insertItemToList(); + } + } + + private insertItemToList = (): void => { + this.validateTag(); + if (!this.uniqueError && this.newTagItem.length) { + this.list.push(this.newTagItem); + this.newTagItem = ""; + this.listChanged.emit(this.list); + } + } + + private deleteItemFromList = (index: number): void => { + this.list.splice(index, 1); + if (Array.isArray(this.isViewOnly)) { + this.isViewOnly = this.isViewOnly.map((i: number) => { + return i > index ? i - 1 : i; + }); + } + } + + private validateTag = (): void => { + this.uniqueError = this.list && this.list.indexOf(this.newTagItem) > -1; + } +} diff --git a/src/angular/tag-cloud/tag-cloud.module.ts b/src/angular/tag-cloud/tag-cloud.module.ts new file mode 100644 index 0000000..fd7efb4 --- /dev/null +++ b/src/angular/tag-cloud/tag-cloud.module.ts @@ -0,0 +1,21 @@ +import { NgModule } from "@angular/core"; +import { TagItemComponent } from "./tag-item/tag-item.component"; +import { TagCloudComponent } from "./tag-cloud.component"; +import { CommonModule } from "@angular/common"; +import { FormElementsModule } from './../form-elements/form-elements.module'; + +@NgModule({ + declarations: [ + TagItemComponent, + TagCloudComponent + ], + imports: [ + CommonModule, + FormElementsModule + ], + exports: [ + TagCloudComponent + ] +}) +export class TagCloudModule { +} diff --git a/src/angular/tag-cloud/tag-item/tag-item.component.html.ts b/src/angular/tag-cloud/tag-item/tag-item.component.html.ts new file mode 100644 index 0000000..04112c1 --- /dev/null +++ b/src/angular/tag-cloud/tag-item/tag-item.component.html.ts @@ -0,0 +1,16 @@ +export default ` +
    + {{text}} + + + + + + + + + + +
    +`; + diff --git a/src/angular/tag-cloud/tag-item/tag-item.component.ts b/src/angular/tag-cloud/tag-item/tag-item.component.ts new file mode 100644 index 0000000..f2e2fa7 --- /dev/null +++ b/src/angular/tag-cloud/tag-item/tag-item.component.ts @@ -0,0 +1,15 @@ +import { Component, EventEmitter, Input, Output, HostBinding } from "@angular/core"; +import template from "./tag-item.component.html"; + +@Component({ + selector: 'sdc-tag-item', + template: template +}) + +export class TagItemComponent { + @HostBinding('class') classes = 'sdc-tag-item'; + @Input() public text: string; + @Input() public isViewOnly: boolean; + @Input() public index: number; + @Output() public clickOnDelete: EventEmitter = new EventEmitter(); +} diff --git a/src/angular/tiles/children/tile-content.component.ts b/src/angular/tiles/children/tile-content.component.ts new file mode 100644 index 0000000..741db26 --- /dev/null +++ b/src/angular/tiles/children/tile-content.component.ts @@ -0,0 +1,10 @@ +import { Component, HostBinding } from "@angular/core"; + +@Component({ + selector: 'sdc-tile-content', + template: '' +}) + +export class TileContentComponent { + @HostBinding('class') classes = 'sdc-tile-content'; +} diff --git a/src/angular/tiles/children/tile-footer.component.ts b/src/angular/tiles/children/tile-footer.component.ts new file mode 100644 index 0000000..519c5ba --- /dev/null +++ b/src/angular/tiles/children/tile-footer.component.ts @@ -0,0 +1,10 @@ +import { Component, HostBinding } from '@angular/core'; + +@Component({ + selector: 'sdc-tile-footer', + template: '' +}) + +export class TileFooterComponent { + @HostBinding('class') classes = 'sdc-tile-footer'; +} diff --git a/src/angular/tiles/children/tile-header.component.ts b/src/angular/tiles/children/tile-header.component.ts new file mode 100644 index 0000000..b040bcb --- /dev/null +++ b/src/angular/tiles/children/tile-header.component.ts @@ -0,0 +1,10 @@ +import { Component, HostBinding } from '@angular/core'; + +@Component({ + selector: "sdc-tile-header", + template: '' +}) + +export class TileHeaderComponent { + @HostBinding('class') classes = 'sdc-tile-header'; +} diff --git a/src/angular/tiles/tile.component.html.ts b/src/angular/tiles/tile.component.html.ts new file mode 100644 index 0000000..81803d5 --- /dev/null +++ b/src/angular/tiles/tile.component.html.ts @@ -0,0 +1,5 @@ +export default ` + + + +`; diff --git a/src/angular/tiles/tile.component.ts b/src/angular/tiles/tile.component.ts new file mode 100644 index 0000000..3791ca0 --- /dev/null +++ b/src/angular/tiles/tile.component.ts @@ -0,0 +1,11 @@ +import { Component, HostBinding } from '@angular/core'; +import template from "./tile.component.html"; + +@Component({ + selector: "sdc-tile", + template: template +}) + +export class TileComponent { + @HostBinding('class') classes = 'sdc-tile'; +} diff --git a/src/angular/tiles/tile.module.ts b/src/angular/tiles/tile.module.ts new file mode 100644 index 0000000..43c750b --- /dev/null +++ b/src/angular/tiles/tile.module.ts @@ -0,0 +1,27 @@ +import { NgModule } from "@angular/core"; +import { TileComponent } from "./tile.component"; +import { CommonModule } from "@angular/common"; +import { TileContentComponent } from "./children/tile-content.component"; +import { TileFooterComponent } from "./children/tile-footer.component"; +import { TileHeaderComponent } from "./children/tile-header.component"; + +@NgModule({ + declarations: [ + TileComponent, + TileContentComponent, + TileFooterComponent, + TileHeaderComponent + ], + imports: [CommonModule], + entryComponents: [TileComponent], + exports: [ + TileComponent, + TileContentComponent, + TileFooterComponent, + TileHeaderComponent + ] +}) + +export class TileModule { + +} diff --git a/src/angular/tooltip/tooltip-template.component.ts b/src/angular/tooltip/tooltip-template.component.ts new file mode 100644 index 0000000..7cb7f72 --- /dev/null +++ b/src/angular/tooltip/tooltip-template.component.ts @@ -0,0 +1,20 @@ +import { Component, ViewChild, ViewContainerRef, AfterViewInit } from '@angular/core'; +import { BehaviorSubject } from 'rxjs/BehaviorSubject'; + +@Component({ + selector: 'tooltip-template', + template: ` +
    + +
    ` +}) + +export class TooltipTemplateComponent implements AfterViewInit { + @ViewChild('templateContainer', {read: ViewContainerRef}) public container: ViewContainerRef; + + public viewReady: BehaviorSubject = new BehaviorSubject(false); + + ngAfterViewInit() : void { + this.viewReady.next(true); + } +} diff --git a/src/angular/tooltip/tooltip.directive.ts b/src/angular/tooltip/tooltip.directive.ts new file mode 100644 index 0000000..77cec62 --- /dev/null +++ b/src/angular/tooltip/tooltip.directive.ts @@ -0,0 +1,459 @@ +import { Directive, ElementRef, HostListener, OnInit, Input, Renderer, TemplateRef } from '@angular/core'; +import { TooltipTemplateComponent } from './tooltip-template.component'; +import { CreateDynamicComponentService } from '../utils/create-dynamic-component.service'; + +const pixel = 'px'; +const leftStyle = 'left'; +const topStyle = 'top'; +const showSuffix = 'show'; +const rightBottomSuffix = 'right__bottom'; +const centerMiddleSuffix = 'center__middle'; + +@Directive({ + selector: '[sdc-tooltip]' +}) +export class TooltipDirective implements OnInit { + @Input('tooltip-text') public text = 'tooltip'; + @Input('tooltip-placement') public placement: TooltipPlacement = TooltipPlacement.Top; + @Input('tooltip-css-class') public customCssClass: string; + @Input('tooltip-template') public template: TemplateRef; + @Input('tooltip-arrow-offset') public arrowOffset: number = 10; + @Input('tooltip-arrow-placement') public arrowPlacement: ArrowPlacement = ArrowPlacement.LeftTop; + @Input('tooltip-offset') public tooltipOffset: number = 3; + + private cssClass: string = 'sdc-tooltip'; // default css class + private tooltip: any; // tooltip html element + private elemPosition: any; + private tooltipTemplateContainer: any; + + private scrollEventHandler = () => {}; + + constructor( + private elementRef: ElementRef, + private service: CreateDynamicComponentService, + private renderer: Renderer) { + + this.elementRef.nativeElement.title = ""; + } + + @HostListener('mouseenter') + public onMouseEnter() { + this.show(); + this.activateScrollEvent(); + } + + @HostListener('mouseleave') + public onMouseLeave() { + this.hide(); + this.deactivateScrollEvent(); + } + + ngOnInit(): void { + this.initScrollEvent(); + } + + private get ScreenWidth() { + return document.documentElement.clientWidth; + } + + private get ScreenHeight() { + return document.documentElement.clientHeight; + } + + private create() { + this.tooltipTemplateContainer = this.service.createComponentDynamically(TooltipTemplateComponent, document.body); + + /** + * Creating a view (injecting our template) from template in our component. + */ + this.tooltip = this.tooltipTemplateContainer.location.nativeElement.querySelector( + '.sdc-tooltip-template-container'); + + if (this.template) { + this.tooltipTemplateContainer.instance.container.createEmbeddedView(this.template); + } else { + this.tooltip.textContent = this.text ? this.text : 'tooltip'; + } + + this.setCssClass(true); + } + + private destroy() { + this.tooltipTemplateContainer.destroy(); + this.tooltip = null; + } + + private show() { + this.create(); + + /** + * View is ready (AfterViewInit event in template component) + */ + this.tooltipTemplateContainer.instance.viewReady.subscribe((isReady) => { + if (isReady) { + this.setPosition(); + this.toggleShowCssClass(true); // add css class + } + }); + } + + private hide() { + this.toggleShowCssClass(false); // remove css class + + this.destroy(); + } + + private toggleShowCssClass(isAdd: boolean) { + if (this.tooltip) { + this.setCssClass(isAdd, '-' + showSuffix); + } + } + + /** + * Adds placement css class and sets tooltip position in style + */ + private setPosition() { + const tooltipPos: IPlacementData = this.getPlacementData(); + + const placementSuffix: string = TooltipPlacement[tooltipPos.placement].toLowerCase(); + + this.setCssClass(true, '-' + placementSuffix); + + this.setAdditionalCssClass(placementSuffix); + + this.renderer.setElementStyle(this.tooltip, topStyle, tooltipPos.top + pixel); + this.renderer.setElementStyle(this.tooltip, leftStyle, tooltipPos.left + pixel); + } + + private setAdditionalCssClass(placementSuffix: string) { + if (this.arrowPlacement === ArrowPlacement.RightBottom) { + this.setCssClass(true, '-' + placementSuffix + '-' + rightBottomSuffix); + } else if (this.arrowPlacement === ArrowPlacement.CenterMiddle) { + this.setCssClass(true, '-' + placementSuffix + '-' + centerMiddleSuffix); + } + } + + private setCssClass(isAdd: boolean, suffix: string = '') { + this.renderer.setElementClass(this.tooltip, this.cssClass + suffix, isAdd); + + if (this.customCssClass) { + this.renderer.setElementClass(this.tooltip, this.customCssClass + suffix, isAdd); + } + } + + /** + * Checks the specified placement (first element in array), if it is not valid - checks other placements + * @returns {IPlacementData} + */ + private getPlacementData(): IPlacementData { + const placement: TooltipPlacement = this.placement; + let tooltipPos: IPlacementData; + + const tooltipPosWithPlacement = this.getPlacement.bind(this, placement); + + // TODO add comments - done + switch (placement) { + case TooltipPlacement.Left: + tooltipPos = tooltipPosWithPlacement( + TooltipPlacement.Right, + TooltipPlacement.Top, + TooltipPlacement.Bottom); + break; + + case TooltipPlacement.Right: + tooltipPos = tooltipPosWithPlacement( + TooltipPlacement.Left, + TooltipPlacement.Top, + TooltipPlacement.Bottom); + break; + + case TooltipPlacement.Top: + tooltipPos = tooltipPosWithPlacement( + TooltipPlacement.Bottom, + TooltipPlacement.Left, + TooltipPlacement.Right); + break; + + case TooltipPlacement.Bottom: + tooltipPos = tooltipPosWithPlacement( + TooltipPlacement.Top, + TooltipPlacement.Left, + TooltipPlacement.Right); + break; + } + + return tooltipPos; + } + + /** + * Returns valid tooltip position data + * @param {TooltipPlacement} placement + * @param {TooltipPlacement} additionalPlacements + * @returns {IPlacementData} + */ + private getPlacement(placement: TooltipPlacement, + ...additionalPlacements: TooltipPlacement[], + ): IPlacementData { + const placements: TooltipPlacement[] = [placement, ...additionalPlacements]; + const filterPlacements = placements + .map((pl) => this.getPosition(pl)) + .filter((item) => this.validatePosition(item)); + return filterPlacements.length > 0 ? filterPlacements[0] : this.getPosition(placement); + } + + /** + * Returns input data for getPosition method + * @returns {ITooltipPositionParams} + */ + private getPlacementInputParams(): ITooltipPositionParams { + this.elemPosition = this.elementRef.nativeElement.getBoundingClientRect(); + + return { + elemHeight: this.elementRef.nativeElement.offsetHeight, + elemLeft: this.elemPosition.left, + elemTop: this.elemPosition.top, + elemWidth: this.elementRef.nativeElement.offsetWidth, + pageYOffset: window.pageYOffset, + tooltipHeight: this.tooltip.offsetHeight, // .clientHeight, + tooltipOffset: this.tooltipOffset, + tooltipWidth: this.tooltip.offsetWidth, + arrowOffset: this.arrowOffset + }; + } + + /** + * Returns tooltip position data + * @param {TooltipPlacement} placement (left, top, right, bottom) + * @returns {IPlacementData} + */ + private getPosition(placement: TooltipPlacement): IPlacementData { + switch(this.arrowPlacement) { + case ArrowPlacement.LeftTop: + return this.getLeftTopPosition(placement); + + case ArrowPlacement.RightBottom: + return this.getRightBottomPosition(placement); + } + + return this.getCenterMiddlePosition(placement); + } + + /** + * Returns tooltip position data (center / middle arrow) + * @param {TooltipPlacement} placement (left, top, right, bottom) + * @returns {IPlacementData} + */ + private getCenterMiddlePosition(placement: TooltipPlacement): IPlacementData { + let left = 0; + let top = 0; + + const inputPos: ITooltipPositionParams = this.getPlacementInputParams(); + switch (placement) { + case TooltipPlacement.Left: + left = inputPos.elemLeft - inputPos.tooltipWidth - inputPos.tooltipOffset; + top = inputPos.elemTop + inputPos.pageYOffset + inputPos.elemHeight / 2 - inputPos.tooltipHeight / 2; + break; + + case TooltipPlacement.Right: + left = inputPos.elemLeft + inputPos.elemWidth + inputPos.tooltipOffset; + top = inputPos.elemTop + inputPos.pageYOffset + inputPos.elemHeight / 2 - inputPos.tooltipHeight / 2; + break; + + case TooltipPlacement.Top: + left = inputPos.elemLeft + inputPos.elemWidth / 2 - inputPos.tooltipWidth / 2; + top = inputPos.elemTop + inputPos.pageYOffset - inputPos.tooltipHeight - inputPos.tooltipOffset; + break; + + case TooltipPlacement.Bottom: + left = inputPos.elemLeft + inputPos.elemWidth / 2 - inputPos.tooltipWidth / 2; + top = inputPos.elemTop + inputPos.pageYOffset + inputPos.elemHeight + inputPos.tooltipOffset; + break; + } + + return { + height: inputPos.tooltipHeight, + left, + placement, + top, + width: inputPos.tooltipWidth, + pageYOffset: inputPos.pageYOffset + } as IPlacementData; + } + + /** + * Returns tooltip position data (left / top arrow) + * @param {TooltipPlacement} placement (left, top, right, bottom) + * @returns {IPlacementData} + */ + private getLeftTopPosition(placement: TooltipPlacement): IPlacementData { + let left = 0; + let top = 0; + + const inputPos: ITooltipPositionParams = this.getPlacementInputParams(); + switch (placement) { + case TooltipPlacement.Left: + left = inputPos.elemLeft - inputPos.tooltipWidth - inputPos.tooltipOffset; + top = inputPos.elemTop + inputPos.pageYOffset + inputPos.elemHeight / 2 - inputPos.arrowOffset; + break; + + case TooltipPlacement.Right: + left = inputPos.elemLeft + inputPos.elemWidth + inputPos.tooltipOffset; + top = inputPos.elemTop + inputPos.pageYOffset + inputPos.elemHeight / 2 - inputPos.arrowOffset; + break; + + case TooltipPlacement.Top: + left = inputPos.elemLeft + inputPos.elemWidth / 2 - inputPos.arrowOffset; + top = inputPos.elemTop + inputPos.pageYOffset - inputPos.tooltipHeight - inputPos.tooltipOffset; + break; + + case TooltipPlacement.Bottom: + left = inputPos.elemLeft + inputPos.elemWidth / 2 - inputPos.arrowOffset; + top = inputPos.elemTop + inputPos.pageYOffset + inputPos.elemHeight + inputPos.tooltipOffset; + break; + } + + return { + height: inputPos.tooltipHeight, + left, + placement, + top, + width: inputPos.tooltipWidth, + pageYOffset: inputPos.pageYOffset + } as IPlacementData; + } + + /** + * Returns tooltip position data (right / bottom arrow) + * @param {TooltipPlacement} placement (left, top, right, bottom) + * @returns {IPlacementData} + */ + private getRightBottomPosition(placement: TooltipPlacement): IPlacementData { + let left = 0; + let top = 0; + + const inputPos: ITooltipPositionParams = this.getPlacementInputParams(); + switch (placement) { + case TooltipPlacement.Left: + left = inputPos.elemLeft - inputPos.tooltipWidth - inputPos.tooltipOffset; + top = inputPos.elemTop + inputPos.pageYOffset + inputPos.elemHeight / 2 - inputPos.tooltipHeight + inputPos.arrowOffset; + break; + + case TooltipPlacement.Right: + left = inputPos.elemLeft + inputPos.elemWidth + inputPos.tooltipOffset; + top = inputPos.elemTop + inputPos.pageYOffset + inputPos.elemHeight / 2 - inputPos.tooltipHeight + inputPos.arrowOffset; + break; + + case TooltipPlacement.Top: + left = inputPos.elemLeft + inputPos.elemWidth / 2 - inputPos.tooltipWidth + inputPos.arrowOffset; + top = inputPos.elemTop + inputPos.pageYOffset - inputPos.tooltipHeight - inputPos.tooltipOffset; + break; + + case TooltipPlacement.Bottom: + left = inputPos.elemLeft + inputPos.elemWidth / 2 - inputPos.tooltipWidth + inputPos.arrowOffset; + top = inputPos.elemTop + inputPos.pageYOffset + inputPos.elemHeight + inputPos.tooltipOffset; + break; + } + + return { + height: inputPos.tooltipHeight, + left, + placement, + top, + width: inputPos.tooltipWidth, + pageYOffset: inputPos.pageYOffset + } as IPlacementData; + } + + /** + * Checks if tooltip position is valid + * @param {IPlacementData} pos + * @returns {boolean} + */ + private validatePosition(pos: IPlacementData): boolean { + if (pos.left < 0 || pos.left + pos.width - 1 > this.ScreenWidth) { + return false; + } + + if (pos.top - pos.pageYOffset < 0 || pos.top - pos.pageYOffset + pos.height - 1 > this.ScreenHeight) { + return false; + } + + return true; + } + + /** + * Scrolling + */ + + private debounce(func: Function, wait: number, immediate?: boolean) { + let timeout; + return function() { + const context = this; + const args = arguments; + const later = () => { + timeout = null; + if (!immediate) { + func.apply(context, args); + } + }; + const callNow = immediate && !timeout; + clearTimeout(timeout); + timeout = setTimeout(later, wait); + if (callNow) { + func.apply(context, args); + } + }; + } + + private initScrollEvent() { + this.scrollEventHandler = this.debounce(() => { + try { + this.setPosition(); + } catch (e) { + + } + }, 10); + } + + private activateScrollEvent() { + window.addEventListener('scroll', this.scrollEventHandler , true); + } + + private deactivateScrollEvent() { + window.removeEventListener('scroll', this.scrollEventHandler , true); + } +} + +export enum TooltipPlacement { + Left, + Right, + Top, + Bottom +} + +export enum ArrowPlacement { + CenterMiddle, + LeftTop, + RightBottom +} + +interface ITooltipPositionParams { + elemLeft: number; + elemTop: number; + elemWidth: number; + elemHeight: number; + tooltipWidth: number; + tooltipHeight: number; + tooltipOffset: number; + pageYOffset: number; + arrowOffset: number; +} + +interface IPlacementData { + left: number; + top: number; + width: number; + height: number; + pageYOffset: number; + placement?: TooltipPlacement; +} diff --git a/src/angular/tooltip/tooltip.module.ts b/src/angular/tooltip/tooltip.module.ts new file mode 100644 index 0000000..a4ad86d --- /dev/null +++ b/src/angular/tooltip/tooltip.module.ts @@ -0,0 +1,17 @@ +import { NgModule } from '@angular/core'; +import { TooltipDirective } from './tooltip.directive'; +import { TooltipTemplateComponent } from './tooltip-template.component'; + +@NgModule({ + declarations: [ + TooltipDirective, + TooltipTemplateComponent + ], + imports: [], + entryComponents: [TooltipTemplateComponent], + exports: [ + TooltipDirective + ], +}) +export class TooltipModule { +} diff --git a/src/angular/utils/create-dynamic-component.service.ts b/src/angular/utils/create-dynamic-component.service.ts new file mode 100644 index 0000000..428dd73 --- /dev/null +++ b/src/angular/utils/create-dynamic-component.service.ts @@ -0,0 +1,101 @@ +import { Injectable, Type, ApplicationRef, ComponentFactoryResolver, ComponentRef, EmbeddedViewRef, Injector } from '@angular/core'; +import { ViewContainerRef } from '@angular/core/src/linker/view_container_ref'; + +@Injectable() +export class CreateDynamicComponentService { + + constructor(private componentFactoryResolver: ComponentFactoryResolver, + private applicationRef: ApplicationRef, + private injector: Injector) { + } + + /** + * Gets the root view container to inject the component to. + * + * @returns {ComponentRef} + * + * @memberOf InjectionService + */ + private getRootViewContainer(): ComponentRef { + const rootComponents = this.applicationRef['_rootComponents']; // Angular2 + // const rootComponents = this.applicationRef['components']; // Angular5 + if (rootComponents.length) { + return rootComponents[0]; + } + throw new Error('View Container not found! ngUpgrade needs to manually set this via setRootViewContainer.'); + } + + /** + * Gets the html element for a component ref. + * + * @param {ComponentRef} componentRef + * @returns {HTMLElement} + * + * @memberOf InjectionService + */ + private getComponentRootNode(componentRef: ComponentRef): HTMLElement { + return (componentRef.hostView as EmbeddedViewRef).rootNodes[0] as HTMLElement; + } + + /** + * Gets the root component container html element. + * + * @returns {HTMLElement} + * + * @memberOf InjectionService + */ + private getRootViewContainerNode(): HTMLElement { + return this.getComponentRootNode(this.getRootViewContainer()); + } + + /** + * Projects the inputs onto the component + * + * @param {ComponentRef} component + * @param {*} options + * @returns {ComponentRef} + * + * @memberOf InjectionService + */ + private projectComponentInputs(component: ComponentRef, options: any): ComponentRef { + if (options) { + const props = Object.getOwnPropertyNames(options); + for (const prop of props) { + component.instance[prop] = options[prop]; + } + } + + return component; + } + + public createComponentDynamically(componentClass: Type, options: any = {}, location: Element = this.getRootViewContainerNode()): ComponentRef { + const componentFactory = this.componentFactoryResolver.resolveComponentFactory(componentClass); + const componentRef = componentFactory.create(this.injector); + const componentRootNode = this.getComponentRootNode(componentRef); + + // project the options passed to the component instance + this.projectComponentInputs(componentRef, options); + this.applicationRef.attachView(componentRef.hostView); + + componentRef.onDestroy(() => { + this.applicationRef.detachView(componentRef.hostView); + }); + + location.appendChild(componentRootNode); + return componentRef; + } + + /** + * Inserts a component into an existing viewContainer + * @param componentType - type of component to create + * @param options - Inputs to project on new component + * @param vcRef - viewContainerRef in which to insert the newly created component + */ + public insertComponentDynamically(componentType: Type, options: any = {}, vcRef: ViewContainerRef): ComponentRef { + const factory = this.componentFactoryResolver.resolveComponentFactory(componentType); + const dynamicComponent = factory.create(vcRef.parentInjector); + this.projectComponentInputs(dynamicComponent, options); + vcRef.insert(dynamicComponent.hostView); + return dynamicComponent; + } +} diff --git a/src/react/Accordion.js b/src/react/Accordion.js new file mode 100644 index 0000000..3acdd24 --- /dev/null +++ b/src/react/Accordion.js @@ -0,0 +1,40 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import SVGIcon from './SVGIcon.js'; + +class Accordion extends React.Component { + constructor(props) { + super(props); + this.state = { + open: props.defaultExpanded + }; + } + render() { + const { children, title, className, dataTestId } = this.props; + const { open } = this.state; + return ( +
    +
    this.setState({ open: !open })} className='sdc-accordion-header'> + +
    {title}
    +
    +
    {children}
    +
    + ); + } +} + +Accordion.propTypes = { + title: PropTypes.string, + children: PropTypes.node, + expandByDefault: PropTypes.bool, + dataTestId: PropTypes.string +}; + +Accordion.defaultProps = { + title: '', + className: '', + defaultExpanded: false +}; + +export default Accordion; diff --git a/src/react/Button.js b/src/react/Button.js new file mode 100644 index 0000000..c628455 --- /dev/null +++ b/src/react/Button.js @@ -0,0 +1,37 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import SVGIcon from './SVGIcon.js'; + +const Button = ({btnType, size, className, iconName, onClick, disabled, children, ...other}) => ( + +); + +Button.propTypes = { + btnType: PropTypes.string, + size: PropTypes.oneOf(['', 'default', 'x-small', 'small', 'medium', 'large']), + className: PropTypes.string, + iconName: PropTypes.string, + onClick: PropTypes.func, + disabled: PropTypes.bool +}; + +Button.defaultProps = { + btnType: 'primary', + size: '', + className: '', + iconName: '', + disabled: false +}; + +export default Button; diff --git a/src/react/Checkbox.js b/src/react/Checkbox.js new file mode 100644 index 0000000..bef6945 --- /dev/null +++ b/src/react/Checkbox.js @@ -0,0 +1,45 @@ +import React from 'react'; + +class Checkbox extends React.Component { + + render() { + let {checked = false, disabled, value, label, inputRef, className, name} = this.props; + let dataTestId = this.props['data-test-id']; + + return ( +
    + +
    + ); + } + + onChange(e) { + let {onChange} = this.props; + if (onChange) { + onChange(e.target.checked); + } + } + + getChecked() { + return this.props.checked; + } + + getValue() { + return this.props.value; + } + +} + +export default Checkbox; diff --git a/src/react/Checklist.js b/src/react/Checklist.js new file mode 100644 index 0000000..1a42aee --- /dev/null +++ b/src/react/Checklist.js @@ -0,0 +1,43 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import Checkbox from './Checkbox.js'; + +const Checklist = ({ items = [], className, onChange }) => ( +
    + {items.map((item, index) => { + return ( +
    + { + let obj = {}; + obj[item.value] = value; + onChange(obj); + }} + data-test-id={item.dataTestId} + /> +
    + ); + })} +
    +); + +Checklist.propTypes = { + items: PropTypes.arrayOf( + PropTypes.shape({ + label: PropTypes.string, + value: PropTypes.string, + checked: PropTypes.bool, + disabled: PropTypes.bool, + dataTestId: PropTypes.string + }) + ), + className: PropTypes.string, + onChange: PropTypes.func +}; + +export default Checklist; \ No newline at end of file diff --git a/src/react/Input.js b/src/react/Input.js new file mode 100644 index 0000000..5760637 --- /dev/null +++ b/src/react/Input.js @@ -0,0 +1,88 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import SVGIcon from './SVGIcon.js'; + +class Input extends React.Component { + + render() { + let {className, disabled, errorMessage, readOnly, label, name, value, type, placeholder, isRequired} = this.props; + let dataTestId = this.props['data-test-id']; + let inputClasses = `sdc-input__input ${errorMessage ? 'error' : ''} ${readOnly ? 'view-only' : ''}`; + let labelClasses = `sdc-input__label ${readOnly ? 'view-only' : ''} ${isRequired ? 'required' : ''}`; + + return ( +
    + + + this.onBlur(e)} + onKeyDown={(e) => this.onKeyDown(e)} + onChange={(e) => this.onChange(e)}/> + { errorMessage &&
    + +
    } +
    + ); + } + + onChange(e) { + let {onChange, readOnly, disabled} = this.props; + if (onChange && !readOnly && !disabled) { + onChange(e.target.value); + } + } + + onBlur(e) { + let {onBlur, readOnly} = this.props; + if (!readOnly && onBlur) { + onBlur(e); + } + } + + onKeyDown(e) { + let {onKeyDown, readOnly} = this.props; + if (!readOnly && onKeyDown) { + onKeyDown(e); + } + } + + getValue() { + return this.props.value; + } + +} +Input.propTypes = { + name: PropTypes.string, + value: PropTypes.string, + type: PropTypes.oneOf(['text', 'number']), + placeholder : PropTypes.string, + onChange: PropTypes.func, + onBlur: PropTypes.func, + onKeyDown: PropTypes.func, + errorMessage: PropTypes.string, + readOnly: PropTypes.bool, + isRequired: PropTypes.bool, + disabled: PropTypes.bool, + label: PropTypes.oneOfType([PropTypes.string, PropTypes.node]), + className: PropTypes.string +}; + +Input.defaultProps = { + type: 'text', + readOnly: false, + isRequired: false, + disabled: false +}; +export default Input; diff --git a/src/react/Modal.js b/src/react/Modal.js new file mode 100644 index 0000000..ab2f7d7 --- /dev/null +++ b/src/react/Modal.js @@ -0,0 +1,55 @@ +import React from 'react'; +import PropTypes from 'prop-types'; + +import Portal from './Portal.js'; +import Body from './ModalBody.js'; +import Header from './ModalHeader.js'; +import Footer from './ModalFooter.js'; +import Title from './ModalTitle.js'; + +export const modalSize = { + medium: 'md', + large: 'l', + extraLarge: 'xl', + small: 'sm', + extraSmall: 'xsm' +}; + + +class Modal extends React.Component { + + render() { + const {size, type, children, show} = this.props; + return ( + +
    { this.modalRef = el;}}> + {show &&
    +
    + {children} +
    +
    } + {show &&
    } +
    + + ); + } +} + +Modal.defaultProps = { + show: false, + size: 'medium', + type: 'info' +}; + +Modal.propTypes = { + show: PropTypes.bool, + size: PropTypes.string, + children: PropTypes.node, + type: PropTypes.string +}; + +Modal.Body = Body; +Modal.Header = Header; +Modal.Footer = Footer; +Modal.Title = Title; +export default Modal; \ No newline at end of file diff --git a/src/react/ModalBody.js b/src/react/ModalBody.js new file mode 100644 index 0000000..4fae0f6 --- /dev/null +++ b/src/react/ModalBody.js @@ -0,0 +1,19 @@ +import React from 'react'; +import PropTypes from 'prop-types'; + +const ModalBody = ({children, className}) => ( +
    + {children} +
    +); + +ModalBody.propTypes = { + children: PropTypes.node, + className: PropTypes.string +}; + +ModalBody.defaultProps = { + className: '' +}; + +export default ModalBody; \ No newline at end of file diff --git a/src/react/ModalFooter.js b/src/react/ModalFooter.js new file mode 100644 index 0000000..607895d --- /dev/null +++ b/src/react/ModalFooter.js @@ -0,0 +1,36 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import Button from './Button.js'; + +const Footer = ({onClose, closeButtonText, actionButtonText, actionButtonClick, withButtons, children}) => { + const closeBtnType = actionButtonClick ? 'secondary' : 'primary'; + return ( +
    + {children} + { + withButtons &&
    + {actionButtonClick && + + } + +
    + } +
    + ); +}; + +Footer.propTypes = { + onClose: PropTypes.func, + closeButtonText: PropTypes.string, + actionButtonText: PropTypes.string, + actionButtonClick: PropTypes.func, + withButtons: PropTypes.bool, + children: PropTypes.node +}; + +Footer.defaultProps = { + closeButtonText: 'Close', + withButtons: true +}; + +export default Footer; \ No newline at end of file diff --git a/src/react/ModalHeader.js b/src/react/ModalHeader.js new file mode 100644 index 0000000..c6be5ef --- /dev/null +++ b/src/react/ModalHeader.js @@ -0,0 +1,41 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import SVGIcon from './SVGIcon.js'; + +const iconMaper = { + error: 'error', + info: 'errorCircle', + alert: 'exclamationTriangleLine' +}; + +const headerTypes = { + error: 'sdc-error__header', + info: 'sdc-info__header', + alert: 'sdc-alert__header', + custom: 'sdc-custom__header' +} + + + +const Header = ({children, onClose, type}) => ( +
    + {type !== 'custom' + && + + + } + {children} + +
    +); + +Header.propTypes = { + children: PropTypes.node, + onClose: PropTypes.func +}; + +Header.defaultProps = { + type: 'info' +}; + +export default Header; \ No newline at end of file diff --git a/src/react/ModalTitle.js b/src/react/ModalTitle.js new file mode 100644 index 0000000..b48cc8a --- /dev/null +++ b/src/react/ModalTitle.js @@ -0,0 +1,19 @@ +import React from 'react'; +import PropTypes from 'prop-types'; + +const Title = ({children, className}) => ( +
    + {children} +
    +); + +Title.PropTypes = { + children: PropTypes.node, + className: PropTypes.string +}; + +Title.defaultProps = { + className: '' +}; + +export default Title; \ No newline at end of file diff --git a/src/react/Panel.js b/src/react/Panel.js new file mode 100644 index 0000000..34d2e62 --- /dev/null +++ b/src/react/Panel.js @@ -0,0 +1,18 @@ +import React from 'react'; +import PropTypes from 'prop-types'; + +const Panel = ({ className, children }) => ( +
    + {children} +
    +); + +Panel.propTypes = { + className: PropTypes.string, + children: PropTypes.node +}; + +Panel.defaultProps = { + className: '' +}; +export default Panel; \ No newline at end of file diff --git a/src/react/PopupMenu.js b/src/react/PopupMenu.js new file mode 100644 index 0000000..d2cd29a --- /dev/null +++ b/src/react/PopupMenu.js @@ -0,0 +1,39 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import PopupMenuItem from './PopupMenuItem'; + +class PopupMenu extends React.Component { + render() { + const {children = [], onMenuItemClick, position = {}, relative} = this.props; + const style = relative ? {left: position.x, top: position.y} : {}; + + return ( +
      + {React.Children.toArray(children).map((child, i) => React.cloneElement(child, + { + onClick: child.props.onClick || onMenuItemClick, + key: i + }))} +
    + ); + } +} + +PopupMenu.propTypes = { + relative: PropTypes.bool, + position: PropTypes.shape({ + x: PropTypes.number, + y: PropTypes.number + }), + onMenuItemClick: PropTypes.func +}; + +PopupMenu.defaultProps = { + relative: false +}; + +export const PopupMenuSeparator = () =>
  • ; + +PopupMenu.Separator = PopupMenuSeparator; +PopupMenu.Item = PopupMenuItem; +export default PopupMenu; diff --git a/src/react/PopupMenuItem.js b/src/react/PopupMenuItem.js new file mode 100644 index 0000000..98e3f49 --- /dev/null +++ b/src/react/PopupMenuItem.js @@ -0,0 +1,34 @@ +import React from 'react'; +import PropTypes from 'prop-types'; + +class PopupMenuItem extends React.Component { + render() { + const {itemId, value, onClick, selected, disabled} = this.props; + const additionalClasses = selected ? 'selected' : disabled ? 'disabled' : ''; + return ( +
  • { + event.stopPropagation(); + onClick && !disabled && onClick(itemId); + }}> + {value} +
  • + ); + } +} + +PopupMenuItem.propTypes = { + itemId: PropTypes.any, + value: PropTypes.any, + selected: PropTypes.bool, + onClick: PropTypes.func, + disabled: PropTypes.bool +}; + +PopupMenuItem.defaultProps = { + selected: false, + disabled: false +}; + +export default PopupMenuItem; diff --git a/src/react/Portal.js b/src/react/Portal.js new file mode 100644 index 0000000..90e0675 --- /dev/null +++ b/src/react/Portal.js @@ -0,0 +1,52 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import ReactDOM from 'react-dom'; + +class Portal extends React.Component { + componentDidMount() { + this.renderPortal(); + } + + componentDidUpdate() { + this.renderPortal(); + } + + componentWillUnmount() { + if (this.defaultNode) { + document.body.removeChild(this.defaultNode); + } + this.defaultNode = null; + this.portal = null; + } + + renderPortal() { + if (!this.defaultNode) { + this.defaultNode = document.createElement('div'); + this.defaultNode.className = 'onap-sdc-portal'; + document.body.appendChild(this.defaultNode); + } + + let children = this.props.children; + if (typeof this.props.children.type === 'function') { + children = React.cloneElement(this.props.children); + } + /** + * Change this to ReactDOM.CreatePortal after upgrading to React 16 + */ + this.portal = ReactDOM.unstable_renderSubtreeIntoContainer( + this, + children, + this.defaultNode + ); + } + render() { + return null; + } + +} + +Portal.propTypes = { + children: PropTypes.node.isRequired +}; + +export default Portal; \ No newline at end of file diff --git a/src/react/Radio.js b/src/react/Radio.js new file mode 100644 index 0000000..483521a --- /dev/null +++ b/src/react/Radio.js @@ -0,0 +1,58 @@ +import React from 'react'; +import PropTypes from 'prop-types'; + +class Radio extends React.Component { + render() { + let {checked, disabled, value, label, className, inputRef, name} = this.props; + let dataTestId = this.props['data-test-id']; + return ( +
    + +
    + ); + } + + onChange(e) { + let {onChange} = this.props; + if (onChange) { + onChange(e.target.checked); + } + } + + getChecked() { + return this.props.checked; + } + + getValue() { + return this.props.value; + } +} + +Radio.propTypes = { + checked: PropTypes.bool, + value: PropTypes.any, + label: PropTypes.string, + className: PropTypes.string, + inputRef: PropTypes.func, + name: PropTypes.string, + disabled: PropTypes.bool +}; + +Radio.defaultProps = { + checked: false, + className: '' +}; + +export default Radio; diff --git a/src/react/RadioGroup.js b/src/react/RadioGroup.js new file mode 100644 index 0000000..59eaca7 --- /dev/null +++ b/src/react/RadioGroup.js @@ -0,0 +1,40 @@ +import React from 'react'; +import Radio from './Radio.js'; + +class RadioGroup extends React.Component { + constructor(props) { + super(props); + this.radios = {}; + } + + render() { + let {name, disabled, title, options, value, className} = this.props; + let dataTestId = this.props['data-test-id']; + return (
    + { title && } +
    + {options.map(option => { + let rName = name + '_' + option.value; + return ( {this.radios[rName] = radio;}} data-test-id={dataTestId + '_' + option.value} + key={rName} value={option.value} + label={option.label} checked={value === option.value} disabled={disabled} + name={name} onChange={() => this.onChange(rName)} /> + );})} +
    +
    ); + } + + onChange(rName) { + let {onChange} = this.props; + let val = this.radios[rName].getValue(); + if (onChange) { + onChange(val); + } + } + + getValue() { + return this.props.value; + } +} + +export default RadioGroup; diff --git a/src/react/SVGIcon.js b/src/react/SVGIcon.js new file mode 100644 index 0000000..8a5b1ae --- /dev/null +++ b/src/react/SVGIcon.js @@ -0,0 +1,47 @@ +import React from 'react'; +import PropTypes from 'prop-types'; + +import iconMap from './utils/iconMap.js'; + +const SVGIcon = ({name, onClick, label, className, iconClassName, labelClassName, labelPosition, color, disabled, ...other}) => { + + let colorClass = (color !== '') ? '__' + color : ''; + let classes = `svg-icon-wrapper ${iconClassName} ${className} ${colorClass} ${onClick ? 'clickable' : ''} ${labelPosition}`; + let camelCasedName = name.replace(/-([a-z])/g, function (g) { return g[1].toUpperCase(); }); + let IconComponent = iconMap[camelCasedName]; + if (!IconComponent) { + console.error('Icon by the name ' + camelCasedName + ' is missing.'); + } + + return ( +
    + { IconComponent && } + { !IconComponent && Missing Icon } + {label && {label}} +
    + ); + +}; + +SVGIcon.propTypes = { + name: PropTypes.string.isRequired, + onClick: PropTypes.func, + label: PropTypes.oneOfType([PropTypes.string, PropTypes.node]), + labelPosition: PropTypes.string, + className: PropTypes.string, + iconClassName: PropTypes.string, + labelClassName: PropTypes.string, + color: PropTypes.string +}; + +SVGIcon.defaultProps = { + name: '', + label: '', + className: '', + iconClassName: '', + labelClassName: '', + labelPosition: 'bottom', + color: '' +}; + +export default SVGIcon; diff --git a/src/react/Tab.js b/src/react/Tab.js new file mode 100644 index 0000000..5aa0f16 --- /dev/null +++ b/src/react/Tab.js @@ -0,0 +1,20 @@ +import React from 'react'; + +class Tab extends React.Component { + render() { + const {activeTab, tabId, title, onClick, disabled, className = ''} = this.props; + const dataTestId = this.props['data-test-id']; + return ( +
  • + {title} +
  • + ); + } +} + +export default Tab; diff --git a/src/react/TabPane.js b/src/react/TabPane.js new file mode 100644 index 0000000..56a4bf0 --- /dev/null +++ b/src/react/TabPane.js @@ -0,0 +1,12 @@ +import React from 'react'; + +class TabPane extends React.Component { + render() { + const {children} = this.props; + return (
    + {children} +
    ); + } +} + +export default TabPane; diff --git a/src/react/Tabs.js b/src/react/Tabs.js new file mode 100644 index 0000000..c502038 --- /dev/null +++ b/src/react/Tabs.js @@ -0,0 +1,29 @@ +import React from 'react'; +import TabPane from './TabPane.js'; + +class Tabs extends React.Component { + render() { + const {type, children = [], activeTab, onTabClick, className} = this.props; + return ( +
    +
      + {children.map(child => React.cloneElement(child, + { + key: child.props.tabId, + onClick: () => onTabClick(child.props.tabId), + activeTab + }))} +
    + + {children.map(child => { + if (child.props.tabId === activeTab) { + return child.props.children; + } + })} + +
    + ); + } +} + +export default Tabs; diff --git a/src/react/Tile.js b/src/react/Tile.js new file mode 100644 index 0000000..f47f88d --- /dev/null +++ b/src/react/Tile.js @@ -0,0 +1,33 @@ +import React, {Children} from 'react'; +import PropTypes from 'prop-types'; +import TileInfo from './TileInfo.js'; +import TileFooter from './TileFooter.js'; +import SVGIcon from './SVGIcon.js'; + +const Tile = ({headerText, headerColor, iconName, iconColor, className, onClick, children, dataTestId}) => { + let childrenArr = Children.toArray(children); + return ( +
    +
    {headerText}
    +
    +
    + {iconName && } +
    + {childrenArr.find(e => e.type === TileInfo)} +
    + {childrenArr.find(e => e.type === TileFooter)} +
    + ); +}; + +Tile.propTypes = { + headerText: PropTypes.string, + headerColor: PropTypes.string, + iconName: PropTypes.string, + iconColor: PropTypes.string, + className: PropTypes.string, + onClick: PropTypes.func, + dataTestId: PropTypes.string +}; + +export default Tile; diff --git a/src/react/TileFooter.js b/src/react/TileFooter.js new file mode 100644 index 0000000..3a56908 --- /dev/null +++ b/src/react/TileFooter.js @@ -0,0 +1,10 @@ +import React, {Children} from 'react'; +import TileFooterCell from './TileFooterCell.js'; + +const TileFooter = ({children, align}) => ( +
    + {Children.toArray(children).filter(e => e.type === TileFooterCell)} +
    +); + +export default TileFooter; diff --git a/src/react/TileFooterCell.js b/src/react/TileFooterCell.js new file mode 100644 index 0000000..37e6416 --- /dev/null +++ b/src/react/TileFooterCell.js @@ -0,0 +1,7 @@ +import React from 'react'; + +const TileFooterCell = ({className, children, dataTestId}) => ( + {children} +); + +export default TileFooterCell; diff --git a/src/react/TileInfo.js b/src/react/TileInfo.js new file mode 100644 index 0000000..bda8e74 --- /dev/null +++ b/src/react/TileInfo.js @@ -0,0 +1,10 @@ +import React, {Children} from 'react'; +import TileInfoLine from './TileInfoLine.js'; + +const TileInfo = ({align, children}) => ( +
    + {Children.toArray(children).filter(e => e.type === TileInfoLine)} +
    +); + +export default TileInfo; diff --git a/src/react/TileInfoLine.js b/src/react/TileInfoLine.js new file mode 100644 index 0000000..5b0e2c9 --- /dev/null +++ b/src/react/TileInfoLine.js @@ -0,0 +1,7 @@ +import React from 'react'; + +const TileInfoLine = ({type, className, children, dataTestId}) => ( +
    {children}
    +); + +export default TileInfoLine; diff --git a/src/react/index.js b/src/react/index.js new file mode 100644 index 0000000..cbe0161 --- /dev/null +++ b/src/react/index.js @@ -0,0 +1,74 @@ +import Accordion from './Accordion.js'; +import Button from './Button.js'; +import Checkbox from './Checkbox.js'; +import Checklist from './Checklist.js'; +import Input from './Input.js'; +import Modal from './Modal.js'; +import ModalBody from './ModalBody.js'; +import ModalFooter from './ModalFooter.js'; +import ModalHeader from './ModalHeader.js'; +import ModalTitle from './ModalTitle.js'; +import Panel from './Panel.js'; +import PopupMenu from './PopupMenu.js'; +import Portal from './Portal.js'; +import Radio from './Radio.js'; +import RadioGroup from './RadioGroup.js'; +import SVGIcon from './SVGIcon.js'; +import Tab from './Tab.js'; +import Tabs from './Tabs.js'; +import Tile from './Tile.js'; +import TileInfo from './TileInfo.js'; +import TileInfoLine from './TileInfoLine.js'; +import TileFooter from './TileFooter.js'; +import TileFooterCell from './TileFooterCell.js'; + + +export { Accordion }; +export { Button }; +export { Checkbox }; +export { Checklist }; +export { Input }; +export { Modal }; +export { ModalBody }; +export { ModalFooter }; +export { ModalHeader }; +export { ModalTitle }; +export { Panel }; +export { PopupMenu }; +export { Portal }; +export { Radio }; +export { RadioGroup }; +export { SVGIcon }; +export { Tab }; +export { Tabs }; +export { Tile }; +export { TileInfo }; +export { TileInfoLine }; +export { TileFooter }; +export { TileFooterCell }; + +export default { + Accordion, + Button, + Checkbox, + Checklist, + Input, + Modal, + ModalBody, + ModalFooter, + ModalHeader, + ModalTitle, + Panel, + PopupMenu, + Portal, + Radio, + RadioGroup, + SVGIcon, + Tab, + Tabs, + Tile, + TileInfo, + TileInfoLine, + TileFooter, + TileFooterCell +}; diff --git a/src/style/scss/_common.scss b/src/style/scss/_common.scss new file mode 100644 index 0000000..7daac20 --- /dev/null +++ b/src/style/scss/_common.scss @@ -0,0 +1,7 @@ +@import "common/normalize"; +@import "common/variables"; +@import "common/mixins"; +@import "common/typography"; +@import "common/base"; +@import "common/icons"; +@import "common/animation"; diff --git a/src/style/scss/_components.scss b/src/style/scss/_components.scss new file mode 100644 index 0000000..3b0d28d --- /dev/null +++ b/src/style/scss/_components.scss @@ -0,0 +1,22 @@ +@import "../../../components/button/button"; +@import "../../../components/tile/tile"; +@import "../../../components/checkbox/checkbox"; +@import "../../../components/radio/radio"; +@import "../../../components/radioGroup/radioGroup"; +@import "../../../components/tabs/tabs"; +@import "../../../components/icon/icon"; +@import "../../../components/input/input"; +@import "../../../components/dropdown/dropdown"; +@import "../../../components/modal/modal"; +@import "../../../components/menu/menu"; +@import "../../../components/filter-bar/_filter-bar"; +@import "../../../components/search-bar/_search-bar"; +@import "../../../components/checklist/checklist"; +@import "../../../components/autocomplete/autocomplete"; +@import "../../../components/tooltip/tooltip"; +@import "../../../components/tag-cloud/_tag-cloud"; +@import "../../../components/notification/notification"; +@import "../../../components/notifications-container/notifications-container"; +@import "../../../components/accordion/accordion"; +@import "../../../components/panel/panel"; +@import "../../../components/validation/validation"; diff --git a/src/style/scss/angular/_svg_icon.scss b/src/style/scss/angular/_svg_icon.scss new file mode 100644 index 0000000..16be14b --- /dev/null +++ b/src/style/scss/angular/_svg_icon.scss @@ -0,0 +1,210 @@ +@mixin color-icon($primary-color) { + color: $primary-color; + fill: $primary-color; +} + +@mixin color-icon-hover($secondary-color) { + &.clickable { + &:not([disabled]):hover, &:active, &:focus { + @include color-icon($secondary-color); + } + } +} + +@mixin color-icon-label($primary-color) { + @include color-icon($primary-color); + + .svg-icon { + @include color-icon($primary-color); + } +} + +@mixin color-icon-label-hover($secondary-color) { + &.clickable { + &:not([disabled]):hover, &:active, &:focus { + @include color-icon-label($secondary-color); + } + } +} + +.svg-icon { + display: inline-flex; + width: 24px; + height: 24px; + + & > svg { + width: 100%; + height: 100%; + } + + &[disabled] { + opacity: 0.7; + } + + &.mode-primary { + @include color-icon($blue); + @include color-icon-hover($light-blue); + } + + &.mode-secondary { + @include color-icon($gray); + @include color-icon-hover($dark-gray); + } + + &.mode-success { + @include color-icon($green); + } + + &.mode-error { + @include color-icon($red); + } + + &.mode-warning { + @include color-icon($yellow); + } + + &.mode-info { + @include color-icon($text-black); + @include color-icon-hover($dark-blue); + } + + &.size-x_small { + width: 12px; + height: 12px; + } + + &.size-small { + width: 16px; + height: 16px; + } + + &.size-medium { + width: 24px; + height: 24px; + } + + &.size-large { + width: 36px; + height: 36px; + } + + &.size-x_large { + width: 48px; + height: 48px; + } +} + +.svg-icon-wrapper { + display: inline-flex; + justify-content: center; + align-items: center; + + &.svg-icon-label { + } + + &.svg-icon { + } + + &[disabled] { + opacity: 0.7; + } + + &.label-placement-bottom { + flex-direction: column; + .svg-icon-label { + margin-top: 0.25em; + } + } + + &.label-placement-right { + .svg-icon-label { + margin-left: 0.25em; + } + } + + &.label-placement-top { + flex-direction: column-reverse; + .svg-icon-label { + margin-bottom: 0.25em; + } + } + + &.label-placement-left { + flex-direction: row-reverse; + .svg-icon-label { + margin-right: 0.25em; + } + } + + &.mode-primary { + @include color-icon-label($blue); + @include color-icon-label-hover($light-blue); + } + + &.mode-secondary { + @include color-icon-label($gray); + @include color-icon-label-hover($dark-gray); + } + + &.mode-success { + @include color-icon-label($green); + } + + &.mode-error { + @include color-icon-label($red); + } + + &.mode-warning { + @include color-icon-label($yellow); + } + + &.mode-info { + @include color-icon-label($text-black); + @include color-icon-label-hover($dark-blue); + } + + &.size-x_small { + font-size: 8px; + line-height: 10px; + + .svg-icon { + @extend .svg-icon.size-x_small; + } + } + + &.size-small { + font-size: 12px; + line-height: 14px; + + .svg-icon { + @extend .svg-icon.size-small; + } + } + + &.size-medium { + font-size: 16px; + line-height: 20px; + + .svg-icon { + @extend .svg-icon.size-medium; + } + } + + &.size-large { + font-size: 24px; + line-height: 28px; + + .svg-icon { + @extend .svg-icon.size-large; + } + } + + &.size-x_large { + font-size: 34px; + line-height: 40px; + + .svg-icon { + @extend .svg-icon.size-x_large; + } + } +} diff --git a/src/style/scss/angular/_tooltip_custom_style.scss b/src/style/scss/angular/_tooltip_custom_style.scss new file mode 100644 index 0000000..886b1dc --- /dev/null +++ b/src/style/scss/angular/_tooltip_custom_style.scss @@ -0,0 +1,9 @@ +.sdc-custom-tooltip { + background-color: $dark-blue; + border-color: $dark-blue; + border-radius: 10px; + + &:after { + border-color: $dark-blue transparent transparent transparent; + } +} diff --git a/src/style/scss/common/_animation.scss b/src/style/scss/common/_animation.scss new file mode 100644 index 0000000..659bd3b --- /dev/null +++ b/src/style/scss/common/_animation.scss @@ -0,0 +1,149 @@ +/*********************************************************************************** + VERTICAL COLLAPSE-EXPEND TRANSITION ANIMATION PAIR. + + We use the 'transition-vertical-collapse' for the collapse/idle block element, + and the 'transition-vertical-expand' to expend that element. + + -important: The element that will be used for the animation should be + a block element, adn have a content or width and height settings for it to work. +*********************************************************************************/ + +/** +Enable to fold an expended block element +@param $offsetY - The top position from which the drop down should fold + */ +@mixin keyframes-expand-animation($name, $maxHeight, $boxShadow:0 0 12px 0px rgba(0,0,0,.3), $margin:0){ + @keyframes #{$name} { + 0% { + opacity: 0; + max-height: 0; + overflow: hidden; + box-shadow: 0 0 0px 0px rgba(0,0,0,.3); + margin:0; + } + 10% { + opacity: 1; + margin: $margin; + } + 50% { + box-shadow: $boxShadow; + } + 99%{ + max-height:$maxHeight; + + overflow: hidden; + } + 100%{ + opacity: 1; + max-height:$maxHeight; + overflow: auto; + } + } +} + +/** +Enable to expend a folded block element +@param $maxHeight - most of the animation is done over the max-height property + so we have to set the maximum height the expended element can expend to. + */ +@mixin keyframes-collapse-animation($name, $maxHeight, $boxShadow:0 0 12px 0px rgba(0,0,0,.3)){ + @keyframes #{$name} { + 0% { + opacity: 1; + max-height:$maxHeight; + box-shadow: $boxShadow; + overflow: hidden; + } + 40%{ + opacity: 1; + } + 99%{ + opacity: 0; + max-height: 0; + overflow: hidden; + box-shadow: 0 0 0px 0px rgba(0,0,0,.3); + } + 100%{ + opacity: 0; + max-height: 0; + overflow: auto; + } + } +} + +/******************************************************************************** + SIMPLE FADE-IN KEYFRAMES ANIMATION (Used in tooltip for example) + + we use 'mixin-keyframes-fade-in-vertically' to create css @keyframes rule that + we later can use with animation property inside our prefered css rules: + .our_class { + ... + animation: keyframes-fade-in-vertically 1s ease-out; + ... + } +*********************************************************************************/ +@mixin mixin-keyframes-fade-in-vertically($fromRelativeHeight, $keyframesName:keyframes-fade-in-vertically){ + @keyframes #{$keyframesName} { + from { + transform: translateY($fromRelativeHeight); + opacity: 0; + } + to { + transform: translateY(0); + opacity: 1; + } + } +} + +/******************************************************************************** + SIMPLE FADE-OUT KEYFRAMES ANIMATION (Opposite of fade-in mixin above) +*********************************************************************************/ +@mixin mixin-keyframes-fade-out-vertically($toRelativeHeight, $keyframesName:keyframes-fade-out-vertically){ + @keyframes #{$keyframesName} { + from { + transform: translateY(0); + opacity: 1; + } + to { + transform: translateY($toRelativeHeight); + opacity: 0; + } + } +} + + + +/******************************************************************************** + RIPPLE ANIMATION (Used for ripple-click directive) +*********************************************************************************/ +@keyframes ripple-animation { + from { + transform: scale(0,0); + opacity: 1; + } + to { + transform: scale(2,2); + opacity: 0; + } +} + +.sdc-ripple-click__animated { + position:relative; +} +.sdc-ripple-click__animated::before{ + display: inline-block; + position:absolute; + top: 0; + left: 0; + content: ''; + animation: ripple-animation .3s ease-out; + background-color: $blue; + width: 14px; + height: 14px; + border-radius: 50%; + pointer-events: none; + opacity: 0; +} + + + diff --git a/src/style/scss/common/_icons.scss b/src/style/scss/common/_icons.scss new file mode 100644 index 0000000..00f425d --- /dev/null +++ b/src/style/scss/common/_icons.scss @@ -0,0 +1,19 @@ +.sdc-icon { + display: inline-block; + text-rendering: auto; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; + width: 16px; + height: 16px; +} + +.sdc-icon-locked {background-image: url("data:image/svg+xml;utf8, "); background-repeat: no-repeat;} +.sdc-icon-plus {background-image: url("data:image/svg+xml;utf8,"); background-repeat: no-repeat;} +.sdc-icon-unlocked {background-image: url("data:image/svg+xml;utf8, "); background-repeat: no-repeat;} +.sdc-icon-vendor {background-image: url("data:image/svg+xml;utf8,vendor"); background-repeat: no-repeat;} +.sdc-icon-vlm {background-image: url("data:image/svg+xml;utf8,vlm_new_icon"); background-repeat: no-repeat;} +.sdc-icon-vsp {background-image: url("data:image/svg+xml;utf8,vsp_new_icon"); background-repeat: no-repeat;} + +.sdc-icon-transform{ + transform: rotate(180deg); +} diff --git a/src/style/scss/common/_normalize.scss b/src/style/scss/common/_normalize.scss new file mode 100644 index 0000000..9375ee9 --- /dev/null +++ b/src/style/scss/common/_normalize.scss @@ -0,0 +1,578 @@ +/* ========================================================================== + Normalize.scss settings + ========================================================================== */ +/** + * Includes legacy browser support IE6/7 + * + * Set to false if you want to drop support for IE6 and IE7 + */ + +$legacy_browser_support: false !default; + +/* Base + ========================================================================== */ + +/** + * 1. Set default font family to sans-serif. + * 2. Prevent iOS and IE text size adjust after device orientation change, + * without disabling user zoom. + * 3. Corrects text resizing oddly in IE 6/7 when body `font-size` is set using + * `em` units. + */ + +html { + font-family: sans-serif; /* 1 */ + -ms-text-size-adjust: 100%; /* 2 */ + -webkit-text-size-adjust: 100%; /* 2 */ + @if $legacy_browser_support { + *font-size: 100%; /* 3 */ + } +} + +/** + * Remove default margin. + */ + +body { + margin: 0; +} + +/* HTML5 display definitions + ========================================================================== */ + +/** + * Correct `block` display not defined for any HTML5 element in IE 8/9. + * Correct `block` display not defined for `details` or `summary` in IE 10/11 + * and Firefox. + * Correct `block` display not defined for `main` in IE 11. + */ + +article, +aside, +details, +figcaption, +figure, +footer, +header, +hgroup, +main, +menu, +nav, +section, +summary { + display: block; +} + +/** + * 1. Correct `inline-block` display not defined in IE 6/7/8/9 and Firefox 3. + * 2. Normalize vertical alignment of `progress` in Chrome, Firefox, and Opera. + */ + +audio, +canvas, +progress, +video { + display: inline-block; /* 1 */ + vertical-align: baseline; /* 2 */ + @if $legacy_browser_support { + *display: inline; + *zoom: 1; + } +} + +/** + * Prevents modern browsers from displaying `audio` without controls. + * Remove excess height in iOS 5 devices. + */ + +audio:not([controls]) { + display: none; + height: 0; +} + +/** + * Address `[hidden]` styling not present in IE 8/9/10. + * Hide the `template` element in IE 8/9/10/11, Safari, and Firefox < 22. + */ + +[hidden], +template { + display: none; +} + +/* Links + ========================================================================== */ + +/** + * Remove the gray background color from active links in IE 10. + */ + +a { + background-color: transparent; +} + +/** + * Improve readability of focused elements when they are also in an + * active/hover state. + */ + +a { + &:active, &:hover { + outline: 0; + }; +} + +/* Text-level semantics + ========================================================================== */ + +/** + * Address styling not present in IE 8/9/10/11, Safari, and Chrome. + */ + +abbr[title] { + border-bottom: 1px dotted; +} + +/** + * Address style set to `bolder` in Firefox 4+, Safari, and Chrome. + */ + +b, +strong { + font-weight: bold; +} + +@if $legacy_browser_support { + blockquote { + margin: 1em 40px; + } +} + +/** + * Address styling not present in Safari and Chrome. + */ + +dfn { + font-style: italic; +} + +/** + * Address variable `h1` font-size and margin within `section` and `article` + * contexts in Firefox 4+, Safari, and Chrome. + */ + +h1 { + font-size: 2em; + margin: 0.67em 0; +} + +@if $legacy_browser_support { + h2 { + font-size: 1.5em; + margin: 0.83em 0; + } + + h3 { + font-size: 1.17em; + margin: 1em 0; + } + + h4 { + font-size: 1em; + margin: 1.33em 0; + } + + h5 { + font-size: 0.83em; + margin: 1.67em 0; + } + + h6 { + font-size: 0.67em; + margin: 2.33em 0; + } +} + +/** + * Addresses styling not present in IE 8/9. + */ + +mark { + background: #ff0; + color: #000; +} + +@if $legacy_browser_support { + + /** + * Addresses margins set differently in IE 6/7. + */ + + p, + pre { + *margin: 1em 0; + } + + /* + * Addresses CSS quotes not supported in IE 6/7. + */ + + q { + *quotes: none; + } + + /* + * Addresses `quotes` property not supported in Safari 4. + */ + + q:before, + q:after { + content: ''; + content: none; + } +} + +/** + * Address inconsistent and variable font size in all browsers. + */ + +small { + font-size: 80%; +} + +/** + * Prevent `sub` and `sup` affecting `line-height` in all browsers. + */ + +sub, +sup { + font-size: 75%; + line-height: 0; + position: relative; + vertical-align: baseline; +} + +sup { + top: -0.5em; +} + +sub { + bottom: -0.25em; +} + +@if $legacy_browser_support { + + /* ========================================================================== + Lists + ========================================================================== */ + + /* + * Addresses margins set differently in IE 6/7. + */ + + dl, + menu, + ol, + ul { + *margin: 1em 0; + } + + dd { + *margin: 0 0 0 40px; + } + + /* + * Addresses paddings set differently in IE 6/7. + */ + + menu, + ol, + ul { + *padding: 0 0 0 40px; + } + + /* + * Corrects list images handled incorrectly in IE 7. + */ + + nav ul, + nav ol { + *list-style: none; + *list-style-image: none; + } + +} + +/* Embedded content + ========================================================================== */ + +/** + * 1. Remove border when inside `a` element in IE 8/9/10. + * 2. Improves image quality when scaled in IE 7. + */ + +img { + border: 0; + @if $legacy_browser_support { + *-ms-interpolation-mode: bicubic; /* 2 */ + } +} + +/** + * Correct overflow not hidden in IE 9/10/11. + */ + +svg:not(:root) { + overflow: hidden; +} + +/* Grouping content + ========================================================================== */ + +/** + * Address margin not present in IE 8/9 and Safari. + */ + +figure { + margin: 1em 40px; +} + +/** + * Address differences between Firefox and other browsers. + */ + +hr { + box-sizing: content-box; + height: 0; +} + +/** + * Contain overflow in all browsers. + */ + +pre { + overflow: auto; +} + +/** + * Address odd `em`-unit font size rendering in all browsers. + * Correct font family set oddly in IE 6, Safari 4/5, and Chrome. + */ + +code, +kbd, +pre, +samp { + font-family: monospace, monospace; + @if $legacy_browser_support { + _font-family: 'courier new', monospace; + } + font-size: 1em; +} + +/* Forms + ========================================================================== */ + +/** + * Known limitation: by default, Chrome and Safari on OS X allow very limited + * styling of `select`, unless a `border` property is set. + */ + +/** + * 1. Correct color not being inherited. + * Known issue: affects color of disabled elements. + * 2. Correct font properties not being inherited. + * 3. Address margins set differently in Firefox 4+, Safari, and Chrome. + * 4. Improves appearance and consistency in all browsers. + */ + +button, +input, +optgroup, +select, +textarea { + color: inherit; /* 1 */ + font: inherit; /* 2 */ + margin: 0; /* 3 */ + @if $legacy_browser_support { + vertical-align: baseline; /* 3 */ + *vertical-align: middle; /* 3 */ + } +} + +/** + * Address `overflow` set to `hidden` in IE 8/9/10/11. + */ + +button { + overflow: visible; +} + +/** + * Address inconsistent `text-transform` inheritance for `button` and `select`. + * All other form control elements do not inherit `text-transform` values. + * Correct `button` style inheritance in Firefox, IE 8/9/10/11, and Opera. + * Correct `select` style inheritance in Firefox. + */ + +button, +select { + text-transform: none; +} + +/** + * 1. Avoid the WebKit bug in Android 4.0.* where (2) destroys native `audio` + * and `video` controls. + * 2. Correct inability to style clickable `input` types in iOS. + * 3. Improve usability and consistency of cursor style between image-type + * `input` and others. + * 4. Removes inner spacing in IE 7 without affecting normal text inputs. + * Known issue: inner spacing remains in IE 6. + */ + +button, +html input[type="button"], /* 1 */ +input[type="reset"], +input[type="submit"] { + -webkit-appearance: button; /* 2 */ + cursor: pointer; /* 3 */ + @if $legacy_browser_support { + *overflow: visible; /* 4 */ + } +} + +/** + * Re-set default cursor for disabled elements. + */ + +button[disabled], +html input[disabled] { + cursor: default; +} + +/** + * Remove inner padding and border in Firefox 4+. + */ + +button::-moz-focus-inner, +input::-moz-focus-inner { + border: 0; + padding: 0; +} + +/** + * Address Firefox 4+ setting `line-height` on `input` using `!important` in + * the UA stylesheet. + */ + +input { + line-height: normal; +} + +/** + * 1. Address box sizing set to `content-box` in IE 8/9/10. + * 2. Remove excess padding in IE 8/9/10. + * Known issue: excess padding remains in IE 6. + */ + +input[type="checkbox"], +input[type="radio"] { + box-sizing: border-box; /* 1 */ + padding: 0; /* 2 */ + @if $legacy_browser_support { + *height: 13px; /* 3 */ + *width: 13px; /* 3 */ + } +} + +/** + * Fix the cursor style for Chrome's increment/decrement buttons. For certain + * `font-size` values of the `input`, it causes the cursor style of the + * decrement button to change from `default` to `text`. + */ + +input[type="number"]::-webkit-inner-spin-button, +input[type="number"]::-webkit-outer-spin-button { + height: auto; +} + +/** + * 1. Address `appearance` set to `searchfield` in Safari and Chrome. + * 2. Address `box-sizing` set to `border-box` in Safari and Chrome. + */ + +input[type="search"] { + -webkit-appearance: textfield; /* 1 */ + box-sizing: content-box; /* 2 */ +} + +/** + * Remove inner padding and search cancel button in Safari and Chrome on OS X. + * Safari (but not Chrome) clips the cancel button when the search input has + * padding (and `textfield` appearance). + */ + +input[type="search"]::-webkit-search-cancel-button, +input[type="search"]::-webkit-search-decoration { + -webkit-appearance: none; +} + +/** + * Define consistent border, margin, and padding. + */ + +fieldset { + border: 1px solid #c0c0c0; + margin: 0 2px; + padding: 0.35em 0.625em 0.75em; +} + +/** + * 1. Correct `color` not being inherited in IE 8/9/10/11. + * 2. Remove padding so people aren't caught out if they zero out fieldsets. + * 3. Corrects text not wrapping in Firefox 3. + * 4. Corrects alignment displayed oddly in IE 6/7. + */ + +legend { + border: 0; /* 1 */ + padding: 0; /* 2 */ + @if $legacy_browser_support { + white-space: normal; /* 3 */ + *margin-left: -7px; /* 4 */ + } +} + +/** + * Remove default vertical scrollbar in IE 8/9/10/11. + */ + +textarea { + overflow: auto; +} + +/** + * Don't inherit the `font-weight` (applied by a rule above). + * NOTE: the default cannot safely be changed in Chrome and Safari on OS X. + */ + +optgroup { + font-weight: bold; +} + +/* Tables + ========================================================================== */ + +/** + * Remove most spacing between table cells. + */ + +table { + border-collapse: collapse; + border-spacing: 0; +} + +td, +th { + padding: 0; +} diff --git a/src/style/scss/common/_typography.scss b/src/style/scss/common/_typography.scss new file mode 100644 index 0000000..6fd59cc --- /dev/null +++ b/src/style/scss/common/_typography.scss @@ -0,0 +1,96 @@ +$heading-font-1: 28px; +$heading-font-2: 24px; +$heading-font-3: 20px; +$heading-font-4: 16px; +$heading-font-5: 14px; + +$body-font-1: 14px; +$body-font-2: 13px; +$body-font-3: 12px; +$body-font-4: 10px; + +@mixin base-font-regular() { + font-family: OpenSans-Regular, Arial, sans-serif; + font-style: normal; + font-weight: 400; +} + +@mixin base-font-italic(){ + font-family: OpenSans-Italic, OpenSans-Regular, Arial, sans-serif; + font-style: normal; + font-weight: 400; +} + +@mixin base-font-semibold() { + font-family: OpenSans-Semibold, Arial, sans-serif; + font-style: normal; + font-weight: 400; +} + +@mixin font-error() { + color: $red; +} + +@mixin heading-1() { + @include base-font-regular; + font-size: $heading-font-1; +} + +@mixin heading-2() { + @include base-font-regular; + font-size: $heading-font-2; +} + +@mixin heading-3 { + @include base-font-regular; + font-size: $heading-font-3; +} + +@mixin heading-4 { + @include base-font-regular; + font-size: $heading-font-4; +} + +@mixin heading-4-emphasis { + @include base-font-semibold; + font-size: $heading-font-4; +} + +@mixin heading-5 { + @include base-font-semibold; + font-size: $heading-font-5; +} + +@mixin body-1 { + @include base-font-regular; + font-size: $body-font-1; +} + +@mixin body-1-italic { + @include base-font-italic; + font-size: $body-font-1; +} + +@mixin body-2 { + @include base-font-regular; + font-size: $body-font-2; +} + +@mixin body-2-emphasis { + @include base-font-semibold; + font-size: $body-font-2; +} + +@mixin body-3 { + @include base-font-regular; + font-size: $body-font-3; +} +@mixin body-3-emphasis { + @include base-font-semibold; + font-size: $body-font-3; +} + +@mixin body-4 { + @include base-font-regular; + font-size: $body-font-4; +} \ No newline at end of file diff --git a/src/style/scss/common/base.scss b/src/style/scss/common/base.scss new file mode 100644 index 0000000..02baf81 --- /dev/null +++ b/src/style/scss/common/base.scss @@ -0,0 +1,96 @@ +html { + font-size: 100%; + height: 100%; +} + +body { + /* scrollbar styling for Internet Explorer */ + scrollbar-face-color: $light-gray; + scrollbar-track-color: $white; + scrollbar-shadow-color:$white; + scrollbar-arrow-color: $gray; + + height: 100%; + @extend %noselect; +} + +/* scrollbar styling for Google Chrome | Safari | Opera */ +::-webkit-scrollbar { + width: 11px; + height: 8px; +} + +::-webkit-scrollbar-track { + background-color: $white; + border: 1px solid $light-gray; + border-top:none; + border-bottom:none; +} + +::-webkit-scrollbar-thumb { + border-radius: 6px; + background-color: $gray; + border: 2px solid rgba(0,0,0,0); + background-clip: padding-box; + + &:hover { + border-width:1px 0px 1px 1px; + } +} + +/* Mozilla Firefox currently doesn't support scrollbar styling */ + +ul { + list-style: none; +} + +h1, h2, h3, h4, h5, h6, ul { + margin: 0; + padding: 0; +} + +input[type='text'] { + padding: 4px; + width: 100%; +} + +input[type="checkbox"] { + width: auto; +} + +input, select, button { + @include body-1; + box-sizing: border-box; +} + +fieldset { + border: none; +} + +fieldset { + label { + display: inline-block; + } +} + +.nav-tabs > li > a:focus, +.btn:focus, +.btn:active:focus, +.btn.active:focus { + outline: none; +} + +.error-message{ + color: $red; + @include body-3; + margin-top: 3px; + &:before{ + content: ""; + display: inline-block; + width: 14px; + height: 14px; + margin-right: 6px; + //not correct icon + background: no-repeat url('data:image/svg+xml;utf8,'); + } +} diff --git a/src/style/scss/common/mixins.scss b/src/style/scss/common/mixins.scss new file mode 100644 index 0000000..c4cb733 --- /dev/null +++ b/src/style/scss/common/mixins.scss @@ -0,0 +1,337 @@ +/* Colors */ +.sdc-bc-white { background-color: $white; } +.sdc-bc-blue { background-color: $blue; } +.sdc-bc-light-blue { background-color: $light-blue; } +.sdc-bc-lighter-blue { background-color: $lighter-blue; } +.sdc-bc-blue-disabled { background-color: $blue-disabled; } +.sdc-bc-dark-blue { background-color: $dark-blue; } +.sdc-bc-black { background-color: $black; } +.sdc-bc-rich-black { background-color: $rich-black; } +.sdc-bc-text-black { background-color: $text-black; } +.sdc-bc-dark-gray { background-color: $dark-gray; } +.sdc-bc-gray { background-color: $gray; } +.sdc-bc-light-gray { background-color: $light-gray; } +.sdc-bc-silver { background-color: $silver; } +.sdc-bc-light-silver { background-color: $light-silver; } +.sdc-bc-green { background-color: $green; } +.sdc-bc-red { background-color: $red; } +.sdc-bc-disabled-red { background-color: $disabled-red; } +.sdc-bc-light-red { background-color: $light-red; } +.sdc-bc-yellow { background-color: $yellow; } +.sdc-bc-dark-purple { background-color: $dark-purple; } +.sdc-bc-purple { background-color: $purple; } +.sdc-bc-light-purple { background-color: $light-purple; } +.sdc-bc-lighter-silver { background-color: $lighter-silver; } +/* Prefix */ +$box-sizing-prefix: webkit moz spec; +$border-radius-prefix: webkit spec; +$box-shadow-radius-prefix: webkit moz spec; +$text-shadow-radius-prefix: spec; +$text-shadow-prefix: spec; +$box-shadow-prefix: all; +$linear-gradient-prefix: all; +$transition-prefix: webkit moz o spec; +$flex-prefix: webkit spec; +$browserPrefixes: webkit moz o ms; + +@mixin prefix($property, $value, $prefixeslist: 'all') { + @if $prefixeslist == all { + -webkit-#{$property}: $value; + -moz-#{$property}: $value; + -ms-#{$property}: $value; + -o-#{$property}: $value; + #{$property}: $value; + } @else { + @each $prefix in $prefixeslist { + @if $prefix == webkit { + -webkit-#{$property}: $value; + } @else if $prefix == moz { + -moz-#{$property}: $value; + } @else if $prefix == ms { + -ms-#{$property}: $value; + } @else if $prefix == o { + -o-#{$property}: $value; + } @else if $prefix == spec { + #{$property}: $value; + } @else { + @warn "No such prefix: #{$prefix}"; + } + } + } +} + +/* Value Prefix*/ +@mixin value-suffix-with-range($property, $valuesuffix, $from, $to, $prefixeslist) { + + @if $prefixeslist == all { + #{property} : -webkit-#{$valuesuffix}($from, $to); + #{property} : -moz-#{$valuesuffix}($from, $to); + #{property} : -o-#{$valuesuffix}($from, $to); + #{property} : -ms-#{$valuesuffix}($from, $to); + + } @else { + @each $prefix in $prefixeslist { + @if $prefix == webkit { + #{property} : -webkit-#{$valuesuffix}($from, $to); + } @else if $prefix == moz { + #{property} : -moz-#{$valuesuffix}($from, $to); + } @else if $prefix == ms { + #{property} : -ms-#{$valuesuffix}($from, $to); + } @else if $prefix == o { + #{property} : -o-#{$valuesuffix}($from, $to); + } @else { + @warn "No such prefix: #{$prefix}"; + } + } + } +} + +/* Box sizing */ +@mixin box-sizing($value: border-box) { + @include prefix(box-sizing, $value, $box-sizing-prefix); +} + +/* Borders & Shadows */ +@mixin box-shadow($value) { + @include prefix(box-shadow, $value, $box-shadow-radius-prefix); +} + +@mixin text-shadow($value) { + @include prefix(text-shadow, $value, $text-shadow-radius-prefix); +} + +@mixin border-radius($value, $positions: all) { + @if ($positions == all) { + @include prefix(border-radius, $value, $border-radius-prefix); + } @else { + @each $position in $positions { + @include prefix(border-#{$position}-radius, $value, $border-radius-prefix); + } + } + +} + +@mixin transition($value) { + @include prefix(transition, $value, $transition-prefix); +} + +/* Opacity */ +@mixin opacity($alpha) { + $ie-opacity: round($alpha * 100); + opacity: $alpha; + filter: unquote("alpha(opacity = #{$ie-opacity})"); +} + +/* Ellipsis */ +@mixin ellipsis($width: 100%, $display: inline-block, $max-width: none) { + overflow: hidden; + text-overflow: ellipsis; + width: $width; + white-space: nowrap; + display: $display; + max-width: $max-width; +} + +@mixin multiline-ellipsis($lineHeight: 1.3em, $lineCount: 2, $bgColor: $white){ + overflow: hidden; + position: relative; + line-height: $lineHeight; + max-height: $lineHeight * $lineCount; + text-align: justify; + // margin-right: -1em; + padding-right: 1em; + &:before { + content: '...'; + position: absolute; + right: 3px; + bottom: 0; + } + &:after { + content: ''; + position: absolute; + right: 0; + width: 1em; + height: 1em; + margin-top: 0.2em; + background: $bgColor; + } +} + +@mixin gradient($from, $to) { + /* fallback/image non-cover color */ + background-color: $from; + background-image: -webkit-gradient(linear, 0% 0%, 0% 100%, from($from), to($to)); + @include value-suffix-with-range(background-color, linear-gradient, $from, $to, $linear-gradient-prefix); +} + +/* Vertical placement of multuple lines of text */ +@mixin vertical-text($height) { + position: absolute; + top: 50%; + margin-top: -$height/2; +} + +@mixin text-vertical-align($align: middle) { + display: table; + width: 100%; + + & > * { + vertical-align: $align; + display: table-cell; + } +} + +@mixin center-element($width) { + width: $width; + margin-left: auto; + margin-right: auto; +} + +@mixin center-content($width) { + & > * { + @include center-element($width); + } +} + +/* transform-rotate */ +// @mixin +// Defines a 2D rotation, the angle is specified in the parameter +// @param +// $deg - angle in degrees +@mixin transform-rotate($deg) { + transform: rotate($deg + deg); /* IE10 and Mozilla */ + -ms-transform: rotate($deg + deg); /* IE 9 */ + -webkit-transform: rotate($deg + deg); /* Safari and Chrome */ +} + +/* transform-translate */ +// @mixin +// Defines a 2D rotation, the angle is specified in the parameter +// @param +// $deg - angle in degrees +@mixin transform-translate($x, $y) { + transform: translate($x, $y); /* IE10 and Mozilla */ + -ms-transform: translate($x, $y); /* IE 9 */ + -webkit-transform: translate($x, $y); /* Safari and Chrome */ +} + +/* transform-scale */ +// @mixin +// Defines a 2D scale transformation, changing the elements width and height +// @param +// $width - width +// @param +// $height - height +@mixin transform-scale($width, $height) { + transform: scale($width, $height); /* IE10 and Mozilla */ + -ms-transform: scale($width, $height); /* IE 9 */ + -webkit-transform: scale($width, $height); /* Safari and Chrome */ +} + +@mixin scrollable() { + ::-webkit-scrollbar { + width: 8px; + } +} + +@mixin create-circle($size, $bgcolor, $content) { + border-radius: 50%; + width: $size; + height: $size; + background: $bgcolor; + border: 3px solid $bgcolor; + &:after { + content: $content; + position: relative; + left: 9px; + top: 9px; + @include base-font-semibold; + font-size: $body-font-1; + } +} + +/**/ +@mixin keyframe-animation($animationType, $properties, $fromValue, $toValue) { + + @keyframes #{$animationType} { + from { + $startIndex: 1; + @each $property in $properties { + #{$property}: nth($fromValue, $startIndex); + $startIndex: $startIndex + 1; + } + } + to { + $startIndex: 1; + @each $property in $properties { + #{$property}: nth($toValue, $startIndex); + $startIndex: $startIndex + 1; + } + } + } + @-moz-keyframes #{$animationType}{ + /* Firefox */ + from { + $startIndex: 1; + @each $property in $properties { + #{$property}: nth($fromValue, $startIndex); + $startIndex: $startIndex + 1; + } + } + to { + $startIndex: 1; + @each $property in $properties { + #{$property}: nth($toValue, $startIndex); + $startIndex: $startIndex + 1; + } + } + } + @-webkit-keyframes #{$animationType} { + /* Safari and Chrome */ + from { + $startIndex: 1; + @each $property in $properties { + #{$property}: nth($fromValue, $startIndex); + $startIndex: $startIndex + 1; + } + } + to { + $startIndex: 1; + @each $property in $properties { + #{$property}: nth($toValue, $startIndex); + $startIndex: $startIndex + 1; + } + } + } + @-o-keyframes #{$animationType} { + /* Opera */ + from { + $startIndex: 1; + @each $property in $properties { + #{$property}: nth($fromValue, $startIndex); + $startIndex: $startIndex + 1; + } + } + to { + $startIndex: 1; + @each $property in $properties { + #{$property}: nth($toValue, $startIndex); + $startIndex: $startIndex + 1; + } + } + } +} + + +/**/ +@mixin border-shadow($xShadow: 0.545px, $yShadow: 0.839px, $blur: 4px, $spread: 0, $color: $light-gray, $opacity: 0.2) { + @include box-shadow($xShadow $yShadow $blur $spread rgba($color, $opacity)); +} + +%noselect { + -webkit-touch-callout: none; + -webkit-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + user-select: none; +} diff --git a/src/style/scss/common/variables.scss b/src/style/scss/common/variables.scss new file mode 100644 index 0000000..38eded4 --- /dev/null +++ b/src/style/scss/common/variables.scss @@ -0,0 +1,35 @@ +// Colors +$black: #000000; +$rich-black: #323943; +$text-black: #191919; +$blue: #009fdb; +$dark-blue: #0568ae; +$light-blue: #1eb9f3; +$lighter-blue: #e6f6fb; +$blue-disabled: #9dd9ef; +$red: #cf2a2a; +$light-red:#ed4141; +$disabled-red:#f4adad; +$purple: #9063cd; +$dark-purple: #702f8a; +$yellow: #ffb81c; +$green: #4ca90c; +$gray: #959595; +$dark-gray: #5a5a5a; +$light-gray: #d2d2d2; +$light-silver: #f2f2f2; +$silver: #eaeaea; + + +$light-purple: #caa2dd; +$lighter-silver: #f8f8f8; +$white: #ffffff; + +$scroll-bar-color: $text-black; + +// Button Sizes +$btn-extra-small: 90px; +$btn-small: 110px; +$btn-medium: 140px; +$btn-large: 180px; +$btn-default: auto; diff --git a/src/style/scss/style.scss b/src/style/scss/style.scss new file mode 100644 index 0000000..5512776 --- /dev/null +++ b/src/style/scss/style.scss @@ -0,0 +1,6 @@ +@import "common"; +@import "components"; + +// for angular +@import "angular/svg_icon"; +@import "angular/tooltip_custom_style"; diff --git a/src/style/scss/themes/1802/_components.scss b/src/style/scss/themes/1802/_components.scss new file mode 100644 index 0000000..6800005 --- /dev/null +++ b/src/style/scss/themes/1802/_components.scss @@ -0,0 +1,23 @@ +/* Deafult theme */ +@import "../../../../../components/tile/tile"; +@import "../../../../../components/checkbox/checkbox"; +@import "../../../../../components/radio/radio"; +@import "../../../../../components/radioGroup/radioGroup"; +@import "../../../../../components/icon/icon"; +@import "../../../../../components/input/input"; +@import "../../../../../components/dropdown/dropdown"; +@import "../../../../../components/menu/menu"; +@import "../../../../../components/filter-bar/_filter-bar"; +@import "../../../../../components/search-bar/_search-bar"; +@import "../../../../../components/checklist/checklist"; +@import "../../../../../components/autocomplete/autocomplete"; +@import "../../../../../components/tooltip/tooltip"; +@import "../../../../../components/tag-cloud/_tag-cloud"; +@import "../../../../../components/notification/notification"; +@import "../../../../../components/notifications-container/notifications-container"; +@import "../../../../../components/validation/validation"; + +/* 1802 theme */ +@import "button"; +@import "modal"; +@import "tabs"; diff --git a/src/style/scss/themes/1802/button.scss b/src/style/scss/themes/1802/button.scss new file mode 100644 index 0000000..05d91d5 --- /dev/null +++ b/src/style/scss/themes/1802/button.scss @@ -0,0 +1,148 @@ +.sdc-button { + @include box-sizing; + display: inline-block; + + outline: none; + border-radius: 2px; + padding: 0 16px; + + height: 32px; + line-height: 32px; + width: 120px; + min-width: 90px; + + cursor: pointer; + text-align: center; + @include body-1; + &:disabled { + cursor: default; + } + + // Primary button + &.sdc-button__primary { + border: none; + background-color: $blue; + color: $white; + + &:not(:disabled) { + &:hover, &:active { + background-color: $light-blue; + } + &:focus:not(:active) { + border: 0.5px solid $white; + background-color: $light-blue; + box-shadow: 0px 0px 0px 1px $light-blue; + } + } + + &:disabled{ + background: $blue-disabled; + } + } + + // Secondary button + &.sdc-button__secondary { + border: 1px solid $light-gray; + background-color: transparent; + color: $text-black; + + &:not(:disabled) { + &:hover, &:active { + background-color: transparent; + color:$text-black; + border: 1px solid $gray; + } + &:focus:not(:active) { + color: $text-black; + box-shadow: inset 0px 0px 0px 0px $light-gray, 0px 0px 0px 1px $gray; + } + } + + &:disabled { + color: $blue-disabled; + border-color: $blue-disabled; + } + } + + // Link button + &.sdc-button__link { + background-color: transparent; + color: $blue; + fill: $blue; + border: none; + + &:not(:disabled) { + &:hover, &:active { + color: $light-blue; + } + &:focus:not(:active) { + border: 1px solid $dark-blue; + color: $light-blue; + } + } + + &:disabled{ + color: $blue-disabled; + } + } + + + // alert button + &.sdc-button__alert { + border: none; + background-color: $red; + color: $white; + + &:not(:disabled) { + &:hover, &:active { + background-color: $light-red; + } + &:focus:not(:active) { + border: 0.5px solid $white; + background-color: $light-red; + box-shadow: 0px 0px 0px 1px $light-red; + } + } + + &:disabled{ + background: $disabled-red; + } + } + + + /*** Sizes ***/ + &.btn-large{ + width: $btn-large; + } + + &.btn-medium{ + width: $btn-medium; + } + + &.btn-small{ + width: $btn-small; + } + + &.btn-x-small{ + width: $btn-extra-small; + } + + &.btn-default{ + width: $btn-default; + } + + /*** Buttons with icons ***/ + .sdc-icon-right{ + margin-left: 15px; + } + + .sdc-icon-left{ + margin-right: 15px; + } + + svg { + display: inline-block; + vertical-align: middle; + } +} + diff --git a/src/style/scss/themes/1802/modal.scss b/src/style/scss/themes/1802/modal.scss new file mode 100644 index 0000000..de99d52 --- /dev/null +++ b/src/style/scss/themes/1802/modal.scss @@ -0,0 +1,193 @@ + +.sdc-modal { + position: fixed; + top: 0; + right: 0; + bottom: 0; + left: 0; + + overflow: auto; + margin: auto; + display: flex; + align-items: center; + z-index: 1001; + + + svg path { + fill: inherit; + } + + .sdc-modal__wrapper { + @include body-1; + background: $white; + width: 100%; + + @include box-shadow(0 0 4px 0 rgba(0,0,0,0.50)); + color: $text-black; + display: flex; + flex-direction: column; + &.sdc-modal-type-info { + border-top: solid 6px $blue; + .sdc-modal__svg-use { + fill: $blue; + } + .svg-icon { + &.__errorCircle { + width: 30px; + height: 30px; + } + } + } + &.sdc-modal-type-alert { + border-top: solid 6px $yellow; + .sdc-modal__svg-use { + fill: $yellow; + } + .svg-icon { + &.__exclamationTriangleLine { + width: 30px; + height: 30px; + } + } + } + &.sdc-modal-type-error { + border-top: solid 6px $red; + .sdc-modal__svg-use { + fill: $red; + } + .svg-icon { + &.__error { + width: 30px; + height: 30px; + } + } + } + &.sdc-modal-type-custom { + padding: 0 30px; + border-radius: 4px; + + .sdc-custom__header { + @include box-sizing; + color: $dark-gray; + height: 50px; + border-bottom: solid 3px $blue; + padding: 0; + + .title { + @include heading-3; + color: $dark-gray; + } + + .sdc-modal__close-button { + margin-top: 0px; + width: 20px; + height: 14px; + } + .sdc-modal__close-button-svg { + width: 20px; + height: 20px; + .sdc-modal__svg-use { + fill: $white; + } + .svg-icon { + height: 14px; + width: 14px; + fill: $white; + } + } + } + .sdc-modal__content { + padding: 20px 40px; + } + } + .sdc-modal__header { + padding: 0px 10px 8px 14px; + display: flex; + justify-content: space-between; + text-align: left; + .sdc-modal__icon { + padding: 20px 12px 0px 6px; + } + + .title { + @include heading-2; + flex: 1 1 auto; + color: $text-black; + padding-top: 19px; + } + + .sdc-modal__close-button { + order:3; + width: 14px; + height: 14px; + margin-top:10px; + cursor: pointer; + .sdc-modal__svg-use { + fill: $black; + } + } + } + .sdc-modal__content { + order:2; + padding-left: 63px; + padding-right: 68px; + padding-bottom: 26px; + } + + .sdc-modal__footer { + order:3; + background-color: $light-silver; + border-top: solid 1px $silver; + padding: 17px 30px; + display: flex; + justify-content: flex-end; + margin: 0 -30px; + button{ + margin-left: 10px; + } + } + } +} + +.modal-background { + position: fixed; + top: 0; + right: 0; + bottom: 0; + left: 0; + background-color: $black; + opacity: 0.70; + z-index: 1000; + + &.show { + z-index: 1000; + opacity: 0.70; + transition: opacity .3s ease, z-index .3s; + } + &.hide { + z-index: -1; + opacity: 0; + transition: opacity .35s ease, z-index .35s; + } +} + +.xl { + width: 1200px; +} + +.l { + width: 875px; +} + +.md { + width: 650px; +} + +.sm { + width: 500px; +} + +.xsm { + width: 432px; +} + diff --git a/src/style/scss/themes/1802/style.scss b/src/style/scss/themes/1802/style.scss new file mode 100644 index 0000000..ae314d8 --- /dev/null +++ b/src/style/scss/themes/1802/style.scss @@ -0,0 +1,5 @@ +@import "../../common"; +@import "components"; + +// for angular +@import "../../angular/svg_icon"; diff --git a/src/style/scss/themes/1802/tabs.scss b/src/style/scss/themes/1802/tabs.scss new file mode 100644 index 0000000..70ee4cb --- /dev/null +++ b/src/style/scss/themes/1802/tabs.scss @@ -0,0 +1,39 @@ +.sdc-tabs { + .sdc-tab { + background-color: $white; + border: 1px solid $silver; + border-left: none; + display: inline-block; + height: 36px; + text-align: center; + cursor: pointer; + padding: 2px 10px 0 10px; + margin: 0; + + + &:first-child { + border-left: 1px solid $silver; + } + &.sdc-tab-active { + background-color: $silver; + } + &[disabled] { + opacity: 0.3; + cursor: default; + } + } + &.sdc-tabs-header { + .sdc-tab { + @include heading-2; + } + } + &.sdc-tabs-menu { + .sdc-tab { + @include body-1; + padding: 0px 10px 4px 10px; + } + } + .sdc-tab-content { + margin-top: 30px; + } +} -- cgit 1.2.3-korg