Skip to content

Commit

Permalink
feat(datepicker): improve default look and feel
Browse files Browse the repository at this point in the history
BREAKING CHANGES: switched to flex layout instead of table-based (drop IE9 support) and prefixed datepicker-related css classes with `ngb-dp-`

Fixes #706
Fixes #1061
Closes #1205
  • Loading branch information
maxokorokov committed Jan 12, 2017
1 parent 6c0b585 commit d88c8b7
Show file tree
Hide file tree
Showing 8 changed files with 195 additions and 104 deletions.
4 changes: 3 additions & 1 deletion src/datepicker/datepicker-day-view.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,16 +40,18 @@ describe('ngbDatepickerDayView', () => {
expect(el).toHaveCssClass('text-muted');
});

it('should apply text-muted style for days of a different month', () => {
it('should apply text-muted and outside classes for days of a different month', () => {
const fixture = TestBed.createComponent(TestComponent);
fixture.detectChanges();

const el = getElement(fixture.nativeElement);
expect(el).not.toHaveCssClass('text-muted');
expect(el).not.toHaveCssClass('outside');

fixture.componentInstance.date = {year: 2016, month: 8, day: 22};
fixture.detectChanges();
expect(el).toHaveCssClass('text-muted');
expect(el).toHaveCssClass('outside');
});

it('should apply selected style', () => {
Expand Down
10 changes: 8 additions & 2 deletions src/datepicker/datepicker-day-view.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,16 +4,22 @@ import {NgbDateStruct} from './ngb-date-struct';
@Component({
selector: '[ngbDatepickerDayView]',
styles: [`
:host {
:host {
text-align: center;
padding: 0.185rem 0.25rem;
width: 2rem;
height: 2rem;
line-height: 2rem;
border-radius: 0.25rem;
}
:host.outside {
opacity: 0.5;
}
`],
host: {
'[class.bg-primary]': 'selected',
'[class.text-white]': 'selected',
'[class.text-muted]': 'isMuted()',
'[class.outside]': 'isMuted()',
'[class.btn-secondary]': '!disabled'
},
template: `{{ date.day }}`
Expand Down
3 changes: 1 addition & 2 deletions src/datepicker/datepicker-input.ts
Original file line number Diff line number Diff line change
Expand Up @@ -222,8 +222,7 @@ export class NgbInputDatepicker implements ControlValueAccessor {

private _applyPopupStyling(nativeElement: any) {
this._renderer.setElementClass(nativeElement, 'dropdown-menu', true);
this._renderer.setElementStyle(nativeElement, 'display', 'block');
this._renderer.setElementStyle(nativeElement, 'padding', '0.40rem');
this._renderer.setElementStyle(nativeElement, 'padding', '0');
}

private _subscribeForDatepickerOutputs(datepickerInstance: NgbDatepicker) {
Expand Down
6 changes: 3 additions & 3 deletions src/datepicker/datepicker-month-view.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,15 +13,15 @@ const createTestComponent = (html: string) =>
createGenericTestComponent(html, TestComponent) as ComponentFixture<TestComponent>;

function getWeekdays(element: HTMLElement): HTMLElement[] {
return <HTMLElement[]>Array.from(element.querySelectorAll('td.weekday'));
return <HTMLElement[]>Array.from(element.querySelectorAll('.ngb-dp-weekday'));
}

function getWeekNumbers(element: HTMLElement): HTMLElement[] {
return <HTMLElement[]>Array.from(element.querySelectorAll('td.weeknumber'));
return <HTMLElement[]>Array.from(element.querySelectorAll('.ngb-dp-week-number'));
}

function getDates(element: HTMLElement): HTMLElement[] {
return <HTMLElement[]>Array.from(element.querySelectorAll('td.day'));
return <HTMLElement[]>Array.from(element.querySelectorAll('.ngb-dp-day'));
}

function expectWeekdays(element: HTMLElement, weekdays: string[]) {
Expand Down
54 changes: 28 additions & 26 deletions src/datepicker/datepicker-month-view.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,45 +6,47 @@ import {DayTemplateContext} from './datepicker-day-template-context';

@Component({
selector: 'ngb-datepicker-month-view',
host: {'class': 'd-block'},
styles: [`
.weekday {
.ngb-dp-weekday, .ngb-dp-week-number {
line-height: 2rem;
}
.weeknumber {
.ngb-dp-day, .ngb-dp-weekday, .ngb-dp-week-number {
width: 2rem;
height: 2rem;
}
.day {
padding: 0;
height: 100%;
.ngb-dp-day {
cursor: pointer;
}
.day.disabled, .day.hidden, .day.collapsed {
.ngb-dp-day.disabled, .ngb-dp-day.hidden, .ngb-dp-day.collapsed {
cursor: default;
}
:host/deep/.day.collapsed > * {
:host/deep/.ngb-dp-day.collapsed > * {
display: none;
}
:host/deep/.day.hidden > * {
:host/deep/.ngb-dp-day.hidden > * {
visibility: hidden;
}
`],
template: `
<table>
<tr *ngIf="showWeekdays">
<td *ngIf="showWeekNumbers"></td>
<td *ngFor="let w of month.weekdays" class="weekday text-center font-weight-bold">{{ i18n.getWeekdayName(w) }}</td>
</tr>
<tr *ngFor="let week of month.weeks">
<td *ngIf="showWeekNumbers" class="weeknumber small text-center">{{ week.number }}</td>
<td *ngFor="let day of week.days" (click)="doSelect(day)" class="day" [class.disabled]="isDisabled(day)"
[class.collapsed]="isCollapsed(day)" [class.hidden]="isHidden(day)">
<template [ngTemplateOutlet]="dayTemplate"
[ngOutletContext]="{date: {year: day.date.year, month: day.date.month, day: day.date.day},
currentMonth: month.number,
disabled: isDisabled(day),
selected: isSelected(day.date)}">
</template>
</td>
</tr>
</table>
<div *ngIf="showWeekdays" class="ngb-dp-week d-flex">
<div *ngIf="showWeekNumbers" class="ngb-dp-weekday"></div>
<div *ngFor="let w of month.weekdays" class="ngb-dp-weekday small text-center text-info font-italic">
{{ i18n.getWeekdayName(w) }}
</div>
</div>
<div *ngFor="let week of month.weeks" class="ngb-dp-week d-flex">
<div *ngIf="showWeekNumbers" class="ngb-dp-week-number small text-center font-italic text-muted">{{ week.number }}</div>
<div *ngFor="let day of week.days" (click)="doSelect(day)" class="ngb-dp-day" [class.disabled]="isDisabled(day)"
[class.collapsed]="isCollapsed(day)" [class.hidden]="isHidden(day)">
<template [ngTemplateOutlet]="dayTemplate"
[ngOutletContext]="{date: {year: day.date.year, month: day.date.month, day: day.date.day},
currentMonth: month.number,
disabled: isDisabled(day),
selected: isSelected(day.date)}">
</template>
</div>
</div>
`
})
export class NgbDatepickerMonthView {
Expand Down
74 changes: 49 additions & 25 deletions src/datepicker/datepicker-navigation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,35 +6,58 @@ import {NgbCalendar} from './ngb-calendar';

@Component({
selector: 'ngb-datepicker-navigation',
host: {'class': 'd-flex justify-content-between', '[class.collapsed]': '!showSelect'},
styles: [`
.collapsed {
margin-bottom: -1.7rem;
:host {
height: 2rem;
line-height: 1.85rem;
}
:host.collapsed {
margin-bottom: -2rem;
}
.ngb-dp-navigation-chevron::before {
border-style: solid;
border-width: 0.2em 0.2em 0 0;
content: '';
display: inline-block;
height: 0.75em;
transform: rotate(-135deg);
-webkit-transform: rotate(-135deg);
-ms-transform: rotate(-135deg);
width: 0.75em;
margin: 0 0 0 0.5rem;
}
.ngb-dp-navigation-chevron.right:before {
-webkit-transform: rotate(45deg);
-ms-transform: rotate(45deg);
transform: rotate(45deg);
margin: 0 0.5rem 0 0;
}
.btn-link {
cursor: pointer;
outline: 0;
}
.btn-link[disabled] {
cursor: not-allowed;
opacity: .65;
}
`],
template: `
<table class="w-100" [class.collapsed]="!showSelect">
<tr>
<td class="text-sm-left">
<button type="button" (click)="doNavigate(navigation.PREV)" class="btn btn-sm btn-secondary btn-inline"
[disabled]="prevDisabled()">&lt;</button>
</td>
<td *ngIf="showSelect">
<ngb-datepicker-navigation-select
[date]="date"
[minDate]="minDate"
[maxDate]="maxDate"
[disabled] = "disabled"
(select)="selectDate($event)">
</ngb-datepicker-navigation-select>
</td>
<td class="text-sm-right">
<button type="button" (click)="doNavigate(navigation.NEXT)" class="next btn btn-sm btn-secondary btn-inline"
[disabled]="nextDisabled()">&gt;</button>
</td>
</tr>
</table>
<button type="button" class="btn-link" (click)="!!doNavigate(navigation.PREV)" [disabled]="prevDisabled()">
<span class="ngb-dp-navigation-chevron"></span>
</button>
<ngb-datepicker-navigation-select *ngIf="showSelect" class="d-block" [style.width.rem]="months * 9"
[date]="date"
[minDate]="minDate"
[maxDate]="maxDate"
[disabled] = "disabled"
(select)="selectDate($event)">
</ngb-datepicker-navigation-select>
<button type="button" class="btn-link" (click)="!!doNavigate(navigation.NEXT)" [disabled]="nextDisabled()">
<span class="ngb-dp-navigation-chevron right"></span>
</button>
`
})
export class NgbDatepickerNavigation {
Expand All @@ -44,6 +67,7 @@ export class NgbDatepickerNavigation {
@Input() disabled: boolean;
@Input() maxDate: NgbDate;
@Input() minDate: NgbDate;
@Input() months: number;
@Input() showSelect: boolean;
@Input() showWeekNumbers: boolean;

Expand Down
73 changes: 54 additions & 19 deletions src/datepicker/datepicker.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ const createTestComponent = (html: string) =>
createGenericTestComponent(html, TestComponent) as ComponentFixture<TestComponent>;

function getDates(element: HTMLElement): HTMLElement[] {
return <HTMLElement[]>Array.from(element.querySelectorAll('td.day'));
return <HTMLElement[]>Array.from(element.querySelectorAll('.ngb-dp-day'));
}

function getDay(element: HTMLElement, index: number): HTMLElement {
Expand Down Expand Up @@ -277,39 +277,35 @@ describe('ngb-datepicker', () => {
const fixture = createTestComponent(
`<ngb-datepicker [startDate]="date" [displayMonths]="1" [navigation]="navigation"></ngb-datepicker>`);

let sections = fixture.debugElement.queryAll(By.css('ngb-datepicker > table > tbody > tr'));
expect(sections.length).toBe(1);
let months = fixture.debugElement.queryAll(By.css('.ngb-dp-month-name'));
expect(months.length).toBe(0);

fixture.componentInstance.navigation = 'arrows';
fixture.detectChanges();
sections = fixture.debugElement.queryAll(By.css('ngb-datepicker > table > tbody > tr'));
expect(sections.length).toBe(2);
expect(sections[0].children.map(c => c.nativeElement.innerText.trim())).toEqual(['Aug 2016']);
months = fixture.debugElement.queryAll(By.css('.ngb-dp-month-name'));
expect(months.length).toBe(1);
expect(months.map(c => c.nativeElement.innerText.trim())).toEqual(['Aug 2016']);

fixture.componentInstance.navigation = 'none';
fixture.detectChanges();
sections = fixture.debugElement.queryAll(By.css('ngb-datepicker > table > tbody > tr'));
expect(sections.length).toBe(2);
expect(sections[0].children.map(c => c.nativeElement.innerText.trim())).toEqual(['Aug 2016']);
months = fixture.debugElement.queryAll(By.css('.ngb-dp-month-name'));
expect(months.length).toBe(1);
expect(months.map(c => c.nativeElement.innerText.trim())).toEqual(['Aug 2016']);
});

it('should always display month names for multiple months', () => {
const fixture = createTestComponent(
`<ngb-datepicker [startDate]="date" [displayMonths]="3" [navigation]="navigation"></ngb-datepicker>`);

let sections = fixture.debugElement.queryAll(By.css('ngb-datepicker > table > tbody > tr'));
expect(sections.length).toBe(2);
expect(sections[0].children.map(c => c.nativeElement.innerText.trim())).toEqual([
'Aug 2016', 'Sep 2016', 'Oct 2016'
]);
let months = fixture.debugElement.queryAll(By.css('.ngb-dp-month-name'));
expect(months.length).toBe(3);
expect(months.map(c => c.nativeElement.innerText.trim())).toEqual(['Aug 2016', 'Sep 2016', 'Oct 2016']);

fixture.componentInstance.navigation = 'arrows';
fixture.detectChanges();
sections = fixture.debugElement.queryAll(By.css('ngb-datepicker > table > tbody > tr'));
expect(sections.length).toBe(2);
expect(sections[0].children.map(c => c.nativeElement.innerText.trim())).toEqual([
'Aug 2016', 'Sep 2016', 'Oct 2016'
]);
months = fixture.debugElement.queryAll(By.css('.ngb-dp-month-name'));
expect(months.length).toBe(3);
expect(months.map(c => c.nativeElement.innerText.trim())).toEqual(['Aug 2016', 'Sep 2016', 'Oct 2016']);
});

it('should emit navigate event when startDate is defined', () => {
Expand Down Expand Up @@ -365,6 +361,44 @@ describe('ngb-datepicker', () => {
.toHaveBeenCalledWith({current: {year: 2016, month: 8}, next: {year: 2015, month: 6}});
});

it('should calculate header dimensions correctly', () => {
const datepicker = TestBed.createComponent(NgbDatepicker).componentInstance;

// 1, 'select', weekdays
expect(datepicker.getHeaderHeight()).toBe(4.25);
expect(datepicker.getHeaderMargin()).toBe(2);

// 1, 'select', no weekdays
datepicker.showWeekdays = false;
expect(datepicker.getHeaderHeight()).toBe(2.25);
expect(datepicker.getHeaderMargin()).toBe(0);

// 1, 'none', no weekdays
datepicker.navigation = 'none';
expect(datepicker.getHeaderHeight()).toBe(2.25);
expect(datepicker.getHeaderMargin()).toBe(2);

// 2, 'none', no weekdays
datepicker.displayMonths = 2;
expect(datepicker.getHeaderHeight()).toBe(2.25);
expect(datepicker.getHeaderMargin()).toBe(2);

// 2, 'select', no weekdays
datepicker.navigation = 'select';
expect(datepicker.getHeaderHeight()).toBe(4.25);
expect(datepicker.getHeaderMargin()).toBe(2);

// 2, 'select', weekdays
datepicker.showWeekdays = true;
expect(datepicker.getHeaderHeight()).toBe(6.25);
expect(datepicker.getHeaderMargin()).toBe(4);

// 2, 'none', weekdays
datepicker.navigation = 'none';
expect(datepicker.getHeaderHeight()).toBe(4.25);
expect(datepicker.getHeaderMargin()).toBe(4);
});

describe('ngModel', () => {

it('should update model based on calendar clicks', () => {
Expand Down Expand Up @@ -650,6 +684,7 @@ class TestComponent {
form = new FormGroup({control: new FormControl('', Validators.required)});
disabledForm = new FormGroup({control: new FormControl({value: null, disabled: true})});
model;
showWeekdays = true;
markDisabled = (date: NgbDateStruct) => { return NgbDate.from(date).equals(new NgbDate(2016, 8, 22)); };
onNavigate = () => {};
}
Loading

0 comments on commit d88c8b7

Please sign in to comment.