Last active
August 13, 2019 07:01
-
-
Save bentedder/136fa7670a8a23617f91be4f9566f96b to your computer and use it in GitHub Desktop.
calendar component angular 4
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<div class="calendar"> | |
<div class="calendar-navs"> | |
<div class="month-nav"> | |
<button (click)="prevMonth()"><</button> | |
<span class="p4">{{ currentDate.format('MMMM') }}</span> | |
<button (click)="nextMonth()">></button> | |
</div> | |
<div class="year-nav"> | |
<button (click)="prevYear()"><</button> | |
<span>{{ currentDate.format('YYYY') }}</span> | |
<button (click)="nextYear()">></button> | |
</div> | |
</div> | |
<div class="month-grid"> | |
<div class="day-names"> | |
<div *ngFor="let name of dayNames" class="day-name p9"> | |
{{ name }} | |
</div> | |
</div> | |
<div class="weeks"> | |
<div *ngFor="let week of weeks" class="week"> | |
<ng-container *ngFor="let day of week"> | |
<div class="week-date disabled" *ngIf="!isSelectedMonth(day.mDate)"> | |
<span class="date-text">{{ day.mDate.date() }}</span> | |
</div> | |
<div class="week-date enabled" | |
*ngIf="isSelectedMonth(day.mDate)" | |
(click)="selectDate(day)" | |
[ngClass]="{ today: day.today, selected: day.selected }"> | |
<span class="date-text">{{ day.mDate.date() }}</span> | |
</div> | |
</ng-container> | |
</div> | |
</div> | |
</div> | |
</div> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
@import '../../styles/vars.scss'; | |
@import '../../styles/typography.scss'; | |
@import '../../styles/colors.scss'; | |
$dayBase: 30px; | |
.calendar { | |
display: block; | |
width: $dayBase * 7; | |
margin: 0 auto; | |
* { | |
box-sizing: border-box; | |
} | |
.calendar-navs { | |
background-color: $cloud; | |
} | |
.month-nav { | |
padding: $base; | |
display: flex; | |
flex-direction: row; | |
justify-content: space-between; | |
} | |
.year-nav { | |
padding: $base; | |
display: flex; | |
flex-direction: row; | |
justify-content: space-between; | |
font-family: 'Montserrat'; | |
} | |
.month-grid { | |
.day-names { | |
display: flex; | |
flex-direction: row; | |
background: $concrete; | |
border-bottom-right-radius: 3px; | |
border-bottom-left-radius: 3px; | |
} | |
.weeks { | |
display: flex; | |
flex-direction: column; | |
} | |
.week { | |
display: flex; | |
flex-direction: row; | |
} | |
.week-date, | |
.day-name { | |
text-align: center; | |
padding: $base; | |
display: block; | |
width: $dayBase; | |
display: flex; | |
justify-content: center; | |
align-items: center; | |
} | |
.week-date { | |
height: $dayBase; | |
position: relative; | |
.date-text { | |
z-index: 10; | |
font-size: 10px; | |
font-family: 'Montserrat', sans-serif; | |
} | |
&::after { | |
content: ''; | |
height: $dayBase * 0.9; | |
width: $dayBase * 0.9; | |
position: absolute; | |
top: 50%; | |
left: 50%; | |
transform: translate(-50%, -50%); | |
border-radius: 50%; | |
transition: background-color 150ms linear, color 150ms linear; | |
z-index: 1; | |
} | |
&.enabled { | |
cursor: pointer; | |
&:hover { | |
&:after { | |
background-color: $seafoam; | |
} | |
} | |
} | |
&.selected { | |
color: $white; | |
&:after { | |
background-color: $teal; | |
} | |
&:hover { | |
&:after { | |
background-color: $teal; | |
} | |
} | |
} | |
&.disabled { | |
color: $light-blue-grey; | |
} | |
} | |
.today { | |
font-weight: bold; | |
} | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import { async, ComponentFixture, TestBed } from '@angular/core/testing'; | |
import * as moment from 'moment'; | |
import { CalendarComponent, CalendarDate } from './calendar.component'; | |
describe('CalendarComponent', () => { | |
let component: CalendarComponent; | |
let fixture: ComponentFixture<CalendarComponent>; | |
beforeEach(async(() => { | |
TestBed.configureTestingModule({ | |
declarations: [ CalendarComponent ] | |
}) | |
.compileComponents(); | |
})); | |
beforeEach(() => { | |
fixture = TestBed.createComponent(CalendarComponent); | |
component = fixture.componentInstance; | |
}); | |
describe('Select date', () => { | |
it ('should emit date when selected', () => { | |
let selectedDate: CalendarDate = { | |
selected: false, | |
today: false, | |
inRange: false, | |
mDate: moment() | |
}; | |
component.onSelectDate.subscribe(x => selectedDate = x); | |
component.selectDate(selectedDate); | |
const isSame = moment(selectedDate.mDate).isSame(moment(selectedDate.mDate), 'day'); | |
expect(isSame).toBeTruthy(); | |
}); | |
}); | |
describe('Calendar grid generation', () => { | |
it('should generate 6 weeks', () => { | |
component.generateCalendar(); | |
fixture.detectChanges(); | |
expect(component.weeks.length).toEqual(6); | |
}); | |
it('should generate Feb 2017 correctly', () => { | |
component.currentDate = moment('2017-02-05'); | |
component.generateCalendar(); | |
fixture.detectChanges(); | |
expect(component.weeks[0][0].mDate.date()).toEqual(29); | |
expect(component.weeks[5][6].mDate.date()).toEqual(11); | |
}); | |
}); | |
describe('Year navigation', () => { | |
it('should go forward 1 year', () => { | |
component.currentDate = moment('2017-09-10'); | |
fixture.detectChanges(); | |
component.nextYear(); | |
fixture.detectChanges(); | |
expect(component.currentDate.year()).toEqual(2018); | |
}); | |
it('should go backward 1 year', () => { | |
component.currentDate = moment('2017-09-10'); | |
fixture.detectChanges(); | |
component.prevYear(); | |
fixture.detectChanges(); | |
expect(component.currentDate.year()).toEqual(2016); | |
}); | |
}); | |
describe('Month navigation', () => { | |
it('should go forward 1', () => { | |
component.currentDate = moment('2017-09-10'); | |
fixture.detectChanges(); | |
component.nextMonth(); | |
fixture.detectChanges(); | |
expect(component.currentDate.month()).toEqual(9); | |
}); | |
it('should go backward 1', () => { | |
component.currentDate = moment('2017-09-10'); | |
fixture.detectChanges(); | |
component.prevMonth(); | |
fixture.detectChanges(); | |
expect(component.currentDate.month()).toEqual(7); | |
}); | |
it('should go to january', () => { | |
component.currentDate = moment('2017-09-10'); | |
fixture.detectChanges(); | |
component.firstMonth(); | |
fixture.detectChanges(); | |
expect(component.currentDate.month()).toEqual(0); | |
}); | |
it('should go to december', () => { | |
component.currentDate = moment('2017-09-10'); | |
fixture.detectChanges(); | |
component.lastMonth(); | |
fixture.detectChanges(); | |
expect(component.currentDate.month()).toEqual(11); | |
}); | |
}); | |
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import { Component, EventEmitter, Input, OnChanges, OnInit, Output, SimpleChanges } from '@angular/core'; | |
import * as moment from 'moment'; | |
import * as _ from 'lodash'; | |
export interface CalendarDate { | |
mDate: moment.Moment; | |
selected?: boolean; | |
today?: boolean; | |
} | |
@Component({ | |
selector: 'yoshimi-calendar', | |
templateUrl: './calendar.component.html', | |
}) | |
export class CalendarComponent implements OnInit, OnChanges { | |
currentDate = moment(); | |
dayNames = ['S', 'M', 'T', 'W', 'T', 'F', 'S']; | |
weeks: CalendarDate[][] = []; | |
sortedDates: CalendarDate[] = []; | |
@Input() selectedDates: CalendarDate[] = []; | |
@Output() onSelectDate = new EventEmitter<CalendarDate>(); | |
constructor() {} | |
ngOnInit(): void { | |
this.generateCalendar(); | |
} | |
ngOnChanges(changes: SimpleChanges): void { | |
if (changes.selectedDates && | |
changes.selectedDates.currentValue && | |
changes.selectedDates.currentValue.length > 1) { | |
// sort on date changes for better performance when range checking | |
this.sortedDates = _.sortBy(changes.selectedDates.currentValue, (m: CalendarDate) => m.mDate.valueOf()); | |
this.generateCalendar(); | |
} | |
} | |
// date checkers | |
isToday(date: moment.Moment): boolean { | |
return moment().isSame(moment(date), 'day'); | |
} | |
isSelected(date: moment.Moment): boolean { | |
return _.findIndex(this.selectedDates, (selectedDate) => { | |
return moment(date).isSame(selectedDate.mDate, 'day'); | |
}) > -1; | |
} | |
isSelectedMonth(date: moment.Moment): boolean { | |
return moment(date).isSame(this.currentDate, 'month'); | |
} | |
selectDate(date: CalendarDate): void { | |
this.onSelectDate.emit(date); | |
} | |
// actions from calendar | |
prevMonth(): void { | |
this.currentDate = moment(this.currentDate).subtract(1, 'months'); | |
this.generateCalendar(); | |
} | |
nextMonth(): void { | |
this.currentDate = moment(this.currentDate).add(1, 'months'); | |
this.generateCalendar(); | |
} | |
firstMonth(): void { | |
this.currentDate = moment(this.currentDate).startOf('year'); | |
this.generateCalendar(); | |
} | |
lastMonth(): void { | |
this.currentDate = moment(this.currentDate).endOf('year'); | |
this.generateCalendar(); | |
} | |
prevYear(): void { | |
this.currentDate = moment(this.currentDate).subtract(1, 'year'); | |
this.generateCalendar(); | |
} | |
nextYear(): void { | |
this.currentDate = moment(this.currentDate).add(1, 'year'); | |
this.generateCalendar(); | |
} | |
// generate the calendar grid | |
generateCalendar(): void { | |
const dates = this.fillDates(this.currentDate); | |
const weeks: CalendarDate[][] = []; | |
while (dates.length > 0) { | |
weeks.push(dates.splice(0, 7)); | |
} | |
this.weeks = weeks; | |
} | |
fillDates(currentMoment: moment.Moment): CalendarDate[] { | |
const firstOfMonth = moment(currentMoment).startOf('month').day(); | |
const firstDayOfGrid = moment(currentMoment).startOf('month').subtract(firstOfMonth, 'days'); | |
const start = firstDayOfGrid.date(); | |
return _.range(start, start + 42) | |
.map((date: number): CalendarDate => { | |
const d = moment(firstDayOfGrid).date(date); | |
return { | |
today: this.isToday(d), | |
selected: this.isSelected(d), | |
mDate: d, | |
}; | |
}); | |
} | |
} |
bindings={
"ng-reflect-ng-for-of": ""
}
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Error in console while loading..