diff --git a/projects/core/src/lib/step-content/step-content.component.html b/projects/core/src/lib/step-content/step-content.component.html new file mode 100644 index 00000000..d00ba301 --- /dev/null +++ b/projects/core/src/lib/step-content/step-content.component.html @@ -0,0 +1,12 @@ +
+ + +
{{title}}
+
{{subtitle}}
+
+ +
+ +
+
+
\ No newline at end of file diff --git a/projects/core/src/lib/step-content/step-content.component.scss b/projects/core/src/lib/step-content/step-content.component.scss new file mode 100644 index 00000000..1e2fa05f --- /dev/null +++ b/projects/core/src/lib/step-content/step-content.component.scss @@ -0,0 +1,29 @@ +:host { + display: block; + width: 100%; + position: relative; +} + +.step-content{ + padding-left: 44px; + padding-bottom: 24px; + +} + +.timeline { + width: 1px; + height: calc(100% - 32px); + position: absolute; + background: var(--fiv-color-timeline, var(--ion-color-medium)); + left: 27.5px; + top: 28px; +} + +.header-title{ + color: var(--fiv-color-title, var(--ion-color-dark)); +} + +.header-subtitle{ + color: var(--fiv-color-subtitle, var(--ion-color-medium)); + font-size: 0.8em; +} \ No newline at end of file diff --git a/projects/core/src/lib/step-content/step-content.component.spec.ts b/projects/core/src/lib/step-content/step-content.component.spec.ts new file mode 100644 index 00000000..a6e1da40 --- /dev/null +++ b/projects/core/src/lib/step-content/step-content.component.spec.ts @@ -0,0 +1,25 @@ +import { async, ComponentFixture, TestBed } from '@angular/core/testing'; + +import { StepContentComponent } from './step-content.component'; + +describe('StepContentComponent', () => { + let component: StepContentComponent; + let fixture: ComponentFixture; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + declarations: [ StepContentComponent ] + }) + .compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(StepContentComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/projects/core/src/lib/step-content/step-content.component.ts b/projects/core/src/lib/step-content/step-content.component.ts new file mode 100644 index 00000000..b2fb39cb --- /dev/null +++ b/projects/core/src/lib/step-content/step-content.component.ts @@ -0,0 +1,52 @@ +import { Component, OnInit, ViewChild, Input, Output, EventEmitter } from '@angular/core'; +import { StepHeaderComponent } from '../step-header/step-header.component'; +import { ExpandableComponent } from '../expandable/expandable.component'; + +@Component({ + selector: 'fiv-step-content', + templateUrl: './step-content.component.html', + styleUrls: ['./step-content.component.scss'] +}) +export class StepContentComponent implements OnInit { + + @Input() index: number; + @Input() icon: string; + @Input() isLast = false; + @Input() title = ''; + @Input() subtitle = ''; + @Output() didOpen: EventEmitter = new EventEmitter(); + @Output() didClose: EventEmitter = new EventEmitter(); + + @ViewChild('self') step: ExpandableComponent; + @ViewChild('header') header: StepHeaderComponent; + + constructor() { + } + + ngOnInit() { } + + open() { + this.step.open(); + } + + close() { + this.step.close(); + } + + complete() { + this.header.complete(); + } + + reset() { + this.header.reset(); + } + + afterClose() { + this.didClose.emit(this); + } + + afterOpen() { + this.didOpen.emit(this); + } + +} diff --git a/projects/core/src/lib/step-header/step-header.component.html b/projects/core/src/lib/step-header/step-header.component.html new file mode 100644 index 00000000..22d56d86 --- /dev/null +++ b/projects/core/src/lib/step-header/step-header.component.html @@ -0,0 +1,9 @@ +
+ {{index}} + +
+
+ + +
\ No newline at end of file diff --git a/projects/core/src/lib/step-header/step-header.component.scss b/projects/core/src/lib/step-header/step-header.component.scss new file mode 100644 index 00000000..715f0a3b --- /dev/null +++ b/projects/core/src/lib/step-header/step-header.component.scss @@ -0,0 +1,27 @@ +:host { + display: block; + width: 100%; + height: 72px; + align-items: center; + position: relative; +} + +.number-container { + width: 24px; + height: 24px; + border-radius: 100%; + position: absolute; + left: 16px; + background: var(--fiv-color-circle, var(--ion-color-primary)); + color: var(--fiv-color-circle-color, var(--ion-color-light)); + * { + position: absolute; + left: 50%; + top: 50%; + transform: translate(-50%, -50%) rotateZ(0deg); + } +} + +.step-content { + padding-left: 56px +} diff --git a/projects/core/src/lib/step-header/step-header.component.spec.ts b/projects/core/src/lib/step-header/step-header.component.spec.ts new file mode 100644 index 00000000..542a020a --- /dev/null +++ b/projects/core/src/lib/step-header/step-header.component.spec.ts @@ -0,0 +1,25 @@ +import { async, ComponentFixture, TestBed } from '@angular/core/testing'; + +import { StepHeaderComponent } from './step-header.component'; + +describe('StepHeaderComponent', () => { + let component: StepHeaderComponent; + let fixture: ComponentFixture; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + declarations: [ StepHeaderComponent ] + }) + .compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(StepHeaderComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/projects/core/src/lib/step-header/step-header.component.ts b/projects/core/src/lib/step-header/step-header.component.ts new file mode 100644 index 00000000..5520c0e9 --- /dev/null +++ b/projects/core/src/lib/step-header/step-header.component.ts @@ -0,0 +1,38 @@ +import { Component, OnInit, Input } from '@angular/core'; +import { rotateAnimation } from '../animations/animations'; + +@Component({ + selector: 'fiv-step-header', + templateUrl: './step-header.component.html', + styleUrls: ['./step-header.component.scss'], + animations: [rotateAnimation] +}) +export class StepHeaderComponent implements OnInit { + + @Input() index: number; + @Input() icon: string; + iconState = 'normal'; + tempIcon: string; + + constructor() { } + + ngOnInit() { + } + + changeIconAndReveal(event, icon: string) { + if (event.fromState === 'normal') { + this.tempIcon = this.icon; + this.icon = icon; + this.iconState = 'normal'; + } + } + + complete() { + this.iconState = 'rotate'; + } + + reset() { + this.icon = this.tempIcon; + } + +} diff --git a/projects/core/src/lib/step/step.component.html b/projects/core/src/lib/step/step.component.html new file mode 100644 index 00000000..5cbbdb3c --- /dev/null +++ b/projects/core/src/lib/step/step.component.html @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/projects/core/src/lib/step/step.component.scss b/projects/core/src/lib/step/step.component.scss new file mode 100644 index 00000000..e69de29b diff --git a/projects/core/src/lib/step/step.component.spec.ts b/projects/core/src/lib/step/step.component.spec.ts new file mode 100644 index 00000000..089c03ae --- /dev/null +++ b/projects/core/src/lib/step/step.component.spec.ts @@ -0,0 +1,25 @@ +import { async, ComponentFixture, TestBed } from '@angular/core/testing'; + +import { StepComponent } from './step.component'; + +describe('StepComponent', () => { + let component: StepComponent; + let fixture: ComponentFixture; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + declarations: [ StepComponent ] + }) + .compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(StepComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/projects/core/src/lib/step/step.component.ts b/projects/core/src/lib/step/step.component.ts new file mode 100644 index 00000000..6034435a --- /dev/null +++ b/projects/core/src/lib/step/step.component.ts @@ -0,0 +1,23 @@ +import { Component, OnInit, Input, ViewChild, TemplateRef } from '@angular/core'; + +@Component({ + selector: 'fiv-step', + templateUrl: './step.component.html', + styleUrls: ['./step.component.scss'] +}) +export class StepComponent implements OnInit { + + @Input() index: number; + @Input() icon: string; + @Input() isLast = false; + @Input() title = ''; + @Input() subtitle = ''; + + @ViewChild(TemplateRef) content: TemplateRef; + + constructor() { } + + ngOnInit() { + } + +} diff --git a/projects/core/src/lib/stepper/README.md b/projects/core/src/lib/stepper/README.md new file mode 100644 index 00000000..80633635 --- /dev/null +++ b/projects/core/src/lib/stepper/README.md @@ -0,0 +1,162 @@ +# Ionic 4 Stepper Component + +A layout component to build steppers (e.g. for forms) in your ionic 4 project (currently in beta) + +![Fiv Stepper Demo GIF](https://github.com/fivethree-team/fivethree/blob/develop/projects/layout/src/lib/fiv-stepper/demo.gif?raw=true) + +## Getting Started + +The stepper component is part of @fivethree/layout package. + +```console +npm install @fivethree/layout +``` + +import LayoutModule inside your AppModule or PageModule. + +```typescript +import { LayoutModule } from '@fivethree/layout'; +... + +@NgModule({ + imports: [ + ... + LayoutModule + ], + declarations: [...] +}) +export class PageModule {} +``` + +### Prerequisites + +This component is build to be used in an ionic 4 CLI project. +You also need to have the angular animation installed in your project. + +```console +npm install @angular/animations +``` + +Peer Dependencies + +```json +"@angular/common": "^6.1.0", +"@angular/core": "^6.1.0", +"@ionic/angular": "^4.0.0-beta.7", +``` + +### Usage + +#### Basic Example + +```html + + + + + + + + ... + + + + + ... + + ... + + + + +``` +...in your pages ts file use the stepper component something like this + +```typescript +import { FivStepperComponent } from '@fivethree/layout'; +... + +@Component({ + ... +}) +export class Page { + + ... + + doStuff(stepper: FivStepperComponent){ + stepper.next(); + } + +} + +``` + +### API + +#### Stepper + +## Methods + +| Method | Parameters | Description | +|------------------| ------------------| ------------------| +| `open` | index: number | open the step at index | +| `close` | index: number | close the step at index | +| `closeAll` | - | close all open steps | +| `select` | index: number | closes all open steps and opens step at index | +| `next` | - | opens the next step (if available) | +| `previous` | - | opens the previous step (if available) | +| `completeStep` | index: number | complete the step at index (if you are using icons in your steps, it will transition to a checkmark) | +| `reset` | index: number | reset a completed step | + +#### Step + +## Input + +| Input | Type | Description | +|------------------| ------------------| ------------------| +| `index` | number | displays the index number in step header | +| `icon` | string | displays an ionicon instead of an index | +| `isLast` | boolean | defaults to false | +| `title` | string | title of step header | +| `subtitle` | string | subtitle of step header | + +## Output + +| Output | Event Data | Description | +|------------------| ------------------| ------------------| +| `onDidOpen` | FivStepComponent | will be emitted when a step has been opened. | +| `onDidClose` | FivStepComponent | will be emitted when a step has been closed. | + + +### Theming + +You can modify the colors of the components by overriding the following css variables + +Colors +* ```--fiv-color-circle: ``` +background color of the step circles (default: var(--ion-color-primary) +* ```--fiv-color-circle-color: ``` + color of the step circles content (index or icon) (default: var(--ion-color-light) +* ```--fiv-color-timeline: ``` +color of the timeline between the steps (default: var(--ion-color-medium) +* ```--fiv-color-title: ``` +color of the step headers title (default: var(--ion-color-dark) +* ```--fiv-color-subtitle: ``` +color of the step headers subtitle (default: var(--ion-color-medium) + + +## Built With + +* [Ionic](http://www.dropwizard.io/1.0.2/docs/) - The web framework used +* [Angular](https://maven.apache.org/) - Dependency Management + + +## Authors + +* **Gary Großgarten** - [garygrossgarten](https://github.com/garygrossgarten) + +See also the list of [contributors](https://github.com/your/project/contributors) who participated in this project. + +## License + +This project is licensed under the MIT License - see the [LICENSE.md](LICENSE.md) file for details diff --git a/projects/core/src/lib/stepper/stepper.component.html b/projects/core/src/lib/stepper/stepper.component.html new file mode 100644 index 00000000..f74ebc97 --- /dev/null +++ b/projects/core/src/lib/stepper/stepper.component.html @@ -0,0 +1,6 @@ + + + + + \ No newline at end of file diff --git a/projects/core/src/lib/stepper/stepper.component.scss b/projects/core/src/lib/stepper/stepper.component.scss new file mode 100644 index 00000000..f44ed0da --- /dev/null +++ b/projects/core/src/lib/stepper/stepper.component.scss @@ -0,0 +1,5 @@ +:host { + display: block; + width: 100%; + height: 100%; +} diff --git a/projects/core/src/lib/stepper/stepper.component.spec.ts b/projects/core/src/lib/stepper/stepper.component.spec.ts new file mode 100644 index 00000000..4bf2213a --- /dev/null +++ b/projects/core/src/lib/stepper/stepper.component.spec.ts @@ -0,0 +1,25 @@ +import { async, ComponentFixture, TestBed } from '@angular/core/testing'; + +import { StepperComponent } from './stepper.component'; + +describe('StepperComponent', () => { + let component: StepperComponent; + let fixture: ComponentFixture; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + declarations: [ StepperComponent ] + }) + .compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(StepperComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/projects/core/src/lib/stepper/stepper.component.ts b/projects/core/src/lib/stepper/stepper.component.ts new file mode 100644 index 00000000..ad20f216 --- /dev/null +++ b/projects/core/src/lib/stepper/stepper.component.ts @@ -0,0 +1,65 @@ +import { StepComponent } from './../step/step.component'; +import { Component, OnInit, ViewChildren, ContentChildren, QueryList } from '@angular/core'; +import { StepContentComponent } from '../step-content/step-content.component'; + +@Component({ + selector: 'fiv-stepper', + templateUrl: './stepper.component.html', + styleUrls: ['./stepper.component.scss'] +}) +export class StepperComponent implements OnInit { + + @ContentChildren(StepComponent) contents: QueryList; + @ViewChildren(StepContentComponent) steps: QueryList; + + currentIndex = 0; + + constructor() { } + + ngOnInit() { + } + + open(index: number) { + this.currentIndex = index; + this.steps.toArray()[index].open(); + } + + close(index: number) { + this.currentIndex = -1; + this.steps.toArray()[index].close(); + } + + select(index: number) { + if (index >= 0 && index < this.steps.length) { + this.closeAll(); + this.open(index); + } + } + + closeAll() { + this.steps.forEach(step => { + step.close(); + }); + } + + next() { + const next = this.currentIndex < this.steps.length ? this.currentIndex + 1 : -1; + console.log('next index', next); + this.select(next); + } + + previous() { + const next = this.currentIndex > 0 ? this.currentIndex + -1 : -1; + console.log('next index', next); + this.select(next); + } + + completeStep(index: number) { + this.steps.toArray()[index].complete(); + } + + reset(index: number) { + this.steps.toArray()[index].reset(); + } + +} diff --git a/src/app/app-routing.module.ts b/src/app/app-routing.module.ts index 5c1ef191..6aeb5391 100644 --- a/src/app/app-routing.module.ts +++ b/src/app/app-routing.module.ts @@ -4,21 +4,18 @@ import { Routes, RouterModule } from '@angular/router'; const routes: Routes = [ { path: '', - redirectTo: 'home', + redirectTo: 'components', pathMatch: 'full' }, { - path: 'home', - loadChildren: './home/home.module#HomePageModule' + path: 'components', + loadChildren: './components/components.module#ComponentsPageModule' }, - { - path: 'list', - loadChildren: './list/list.module#ListPageModule' - } + { path: 'loading', loadChildren: './loading/loading.module#LoadingPageModule' } ]; @NgModule({ imports: [RouterModule.forRoot(routes)], exports: [RouterModule] }) -export class AppRoutingModule {} +export class AppRoutingModule { } diff --git a/src/app/app.component.html b/src/app/app.component.html index 7ae20258..1ca7fb7e 100644 --- a/src/app/app.component.html +++ b/src/app/app.component.html @@ -8,14 +8,16 @@ - - - - - {{p.title}} - - - + + + + + + {{p.title}} + + + + diff --git a/src/app/app.component.ts b/src/app/app.component.ts index 2506473d..8049053c 100644 --- a/src/app/app.component.ts +++ b/src/app/app.component.ts @@ -11,14 +11,14 @@ import { StatusBar } from '@ionic-native/status-bar/ngx'; export class AppComponent { public appPages = [ { - title: 'Home', - url: '/home', + title: 'Components', + url: '/components', icon: 'home' }, { - title: 'List', - url: '/list', - icon: 'list' + title: 'Loading', + url: '/loading', + icon: 'home' } ]; diff --git a/src/app/app.module.ts b/src/app/app.module.ts index 9350f036..af919f8d 100644 --- a/src/app/app.module.ts +++ b/src/app/app.module.ts @@ -1,6 +1,7 @@ -import { CoreModule } from 'dist/core'; +import { FivethreeCoreModule } from 'core'; import { NgModule } from '@angular/core'; import { BrowserModule } from '@angular/platform-browser'; +import { BrowserAnimationsModule } from '@angular/platform-browser/animations'; import { RouterModule, RouteReuseStrategy, Routes } from '@angular/router'; import { IonicModule, IonicRouteStrategy } from '@ionic/angular'; @@ -14,7 +15,8 @@ import { AppRoutingModule } from './app-routing.module'; entryComponents: [], imports: [ BrowserModule, - CoreModule, + BrowserAnimationsModule, + FivethreeCoreModule, IonicModule.forRoot(), AppRoutingModule ], diff --git a/src/app/components/components.module.ts b/src/app/components/components.module.ts new file mode 100644 index 00000000..dd4d3045 --- /dev/null +++ b/src/app/components/components.module.ts @@ -0,0 +1,28 @@ +import { NgModule } from '@angular/core'; +import { CommonModule } from '@angular/common'; +import { FormsModule } from '@angular/forms'; +import { Routes, RouterModule } from '@angular/router'; + +import { IonicModule } from '@ionic/angular'; + +import { ComponentsPage } from './components.page'; +import { FivethreeCoreModule } from 'core'; + +const routes: Routes = [ + { + path: '', + component: ComponentsPage + } +]; + +@NgModule({ + imports: [ + CommonModule, + FormsModule, + IonicModule, + FivethreeCoreModule, + RouterModule.forChild(routes) + ], + declarations: [ComponentsPage] +}) +export class ComponentsPageModule {} diff --git a/src/app/components/components.page.html b/src/app/components/components.page.html new file mode 100644 index 00000000..01d9e893 --- /dev/null +++ b/src/app/components/components.page.html @@ -0,0 +1,104 @@ + + + + + + Components + + + + + + Expandable + + + + + Awesome Label + + + + Apple + Banana + Pineapple + + + + Stepper + + + + + + Awesome Subtitle + Awesome Title + + + + + + + Close + + + reset + + + Complete + + + NEXT + + + + + + + + + + + Awesome Subtitle + Awesome Title + + + + + + + Previous + + + NEXT + + + + + + + + + + + Awesome Subtitle + Awesome Title + + + + + + + Previous + + + NEXT + + + + + + + + + + \ No newline at end of file diff --git a/src/app/components/components.page.scss b/src/app/components/components.page.scss new file mode 100644 index 00000000..e69de29b diff --git a/src/app/components/components.page.spec.ts b/src/app/components/components.page.spec.ts new file mode 100644 index 00000000..a5a91771 --- /dev/null +++ b/src/app/components/components.page.spec.ts @@ -0,0 +1,27 @@ +import { CUSTOM_ELEMENTS_SCHEMA } from '@angular/core'; +import { async, ComponentFixture, TestBed } from '@angular/core/testing'; + +import { ComponentsPage } from './components.page'; + +describe('ComponentsPage', () => { + let component: ComponentsPage; + let fixture: ComponentFixture; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + declarations: [ ComponentsPage ], + schemas: [CUSTOM_ELEMENTS_SCHEMA], + }) + .compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(ComponentsPage); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/components/components.page.ts b/src/app/components/components.page.ts new file mode 100644 index 00000000..5247f292 --- /dev/null +++ b/src/app/components/components.page.ts @@ -0,0 +1,15 @@ +import { Component, OnInit } from '@angular/core'; + +@Component({ + selector: 'app-components', + templateUrl: './components.page.html', + styleUrls: ['./components.page.scss'], +}) +export class ComponentsPage implements OnInit { + + constructor() { } + + ngOnInit() { + } + +} diff --git a/src/app/loading/loading.module.ts b/src/app/loading/loading.module.ts new file mode 100644 index 00000000..d850cdcd --- /dev/null +++ b/src/app/loading/loading.module.ts @@ -0,0 +1,26 @@ +import { NgModule } from '@angular/core'; +import { CommonModule } from '@angular/common'; +import { FormsModule } from '@angular/forms'; +import { Routes, RouterModule } from '@angular/router'; + +import { IonicModule } from '@ionic/angular'; + +import { LoadingPage } from './loading.page'; + +const routes: Routes = [ + { + path: '', + component: LoadingPage + } +]; + +@NgModule({ + imports: [ + CommonModule, + FormsModule, + IonicModule, + RouterModule.forChild(routes) + ], + declarations: [LoadingPage] +}) +export class LoadingPageModule {} diff --git a/src/app/loading/loading.page.html b/src/app/loading/loading.page.html new file mode 100644 index 00000000..825e0ec3 --- /dev/null +++ b/src/app/loading/loading.page.html @@ -0,0 +1,9 @@ + + + loading + + + + + + diff --git a/src/app/loading/loading.page.scss b/src/app/loading/loading.page.scss new file mode 100644 index 00000000..e69de29b diff --git a/src/app/loading/loading.page.spec.ts b/src/app/loading/loading.page.spec.ts new file mode 100644 index 00000000..ab9f6456 --- /dev/null +++ b/src/app/loading/loading.page.spec.ts @@ -0,0 +1,27 @@ +import { CUSTOM_ELEMENTS_SCHEMA } from '@angular/core'; +import { async, ComponentFixture, TestBed } from '@angular/core/testing'; + +import { LoadingPage } from './loading.page'; + +describe('LoadingPage', () => { + let component: LoadingPage; + let fixture: ComponentFixture; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + declarations: [ LoadingPage ], + schemas: [CUSTOM_ELEMENTS_SCHEMA], + }) + .compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(LoadingPage); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/loading/loading.page.ts b/src/app/loading/loading.page.ts new file mode 100644 index 00000000..3de70efb --- /dev/null +++ b/src/app/loading/loading.page.ts @@ -0,0 +1,15 @@ +import { Component, OnInit } from '@angular/core'; + +@Component({ + selector: 'app-loading', + templateUrl: './loading.page.html', + styleUrls: ['./loading.page.scss'], +}) +export class LoadingPage implements OnInit { + + constructor() { } + + ngOnInit() { + } + +}