Skip to content

Commit

Permalink
feat(datepicker): improve date and time inputs combination (#3117)
Browse files Browse the repository at this point in the history
  • Loading branch information
sashaqred authored Oct 3, 2022
1 parent 5740682 commit 8387967
Show file tree
Hide file tree
Showing 20 changed files with 767 additions and 161 deletions.
24 changes: 24 additions & 0 deletions src/app/playground-components.ts
Original file line number Diff line number Diff line change
Expand Up @@ -544,6 +544,24 @@ export const PLAYGROUND_COMPONENTS: ComponentLink[] = [
component: 'DateTimepickerShowcaseComponent',
name: 'Date Timepicker Showcase',
},
{
path: 'date-timepicker-dynamic-inputs-showcase.component',
link: '/datepicker/date-timepicker-dynamic-inputs-showcase.component',
component: 'DateTimepickerDynamicInputsShowcaseComponent',
name: 'Date Timepicker Dynamic Inputs Showcase',
},
{
path: 'datepicker-dynamic-inputs-showcase.component',
link: '/datepicker/datepicker-dynamic-inputs-showcase.component',
component: 'DatepickerDynamicInputsShowcaseComponent',
name: 'Datepicker Dynamic Inputs Showcase',
},
{
path: 'range-picker-dynamic-inputs-showcase.component',
link: '/datepicker/rangepicker-dynamic-inputs-showcase.component',
component: 'RangeickerDynamicInputsShowcaseComponent',
name: 'Range Picker Dynamic Inputs Showcase',
},
{
path: 'date-timepicker-single-column.component',
link: '/datepicker/date-timepicker-single-column.component',
Expand Down Expand Up @@ -597,6 +615,12 @@ export const PLAYGROUND_COMPONENTS: ComponentLink[] = [
component: 'TimepickerWithSecondsComponent',
name: 'Timepicker With Seconds',
},
{
path: 'timepicker-dynamic-inputs-showcase.component',
link: '/timepicker/timepicker-dynamic-inputs-showcase.component',
component: 'TimepickerDynamicInputsShowcaseComponent',
name: 'Timepicker Dynamic Inputs Showcase',
},
{
path: 'timepicker-form-control.component',
link: '/timepicker/timepicker-form-control.component',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,21 +35,26 @@ import { NbTimePickerComponent } from '../timepicker/timepicker.component';
[showNavigation]="showNavigation"
[showWeekNumber]="showWeekNumber"
[weekNumberSymbol]="weekNumberSymbol"
(dateChange)="onDateValueChange($event)">
(dateChange)="onDateValueChange($event)"
>
</nb-base-calendar>
<div class="timepicker-section"
[class.size-large]="isLarge()"
[class.timepicker-single-column-width]="singleColumn"
[class.timepicker-multiple-column-width]="!singleColumn">
<div
class="timepicker-section"
[class.size-large]="isLarge()"
[class.timepicker-single-column-width]="singleColumn"
[class.timepicker-multiple-column-width]="!singleColumn"
>
<div class="picker-title">{{ title }}</div>
<nb-timepicker
(onSelectTime)="onTimeChange($event)"
[date]="date"
[twelveHoursFormat]="twelveHoursFormat"
[showAmPmLabel]="showAmPmLabel"
[withSeconds]="showSeconds()"
[showFooter]="false"
[singleColumn]="singleColumn"
[step]="step">
[step]="step"
>
</nb-timepicker>
<ng-container nbPortalOutlet></ng-container>
</div>
Expand Down Expand Up @@ -79,6 +84,11 @@ export class NbCalendarWithTimeComponent<D> extends NbCalendarComponent<D> imple
* */
@Input() twelveHoursFormat: boolean;

/**
* Defines should show am/pm label if twelveHoursFormat enabled.
* */
@Input() showAmPmLabel: boolean;

/**
* Show seconds in timepicker.
* Ignored when singleColumn is true.
Expand Down Expand Up @@ -115,9 +125,10 @@ export class NbCalendarWithTimeComponent<D> extends NbCalendarComponent<D> imple
@ViewChild(NbPortalOutletDirective) portalOutlet: NbPortalOutletDirective;
@ViewChild(NbTimePickerComponent) timepicker: NbTimePickerComponent<D>;

constructor(protected dateService: NbDateService<D>,
public cd: ChangeDetectorRef,
protected calendarTimeModelService: NbCalendarTimeModelService<D>,
constructor(
protected dateService: NbDateService<D>,
public cd: ChangeDetectorRef,
protected calendarTimeModelService: NbCalendarTimeModelService<D>,
) {
super();
}
Expand Down Expand Up @@ -166,11 +177,8 @@ export class NbCalendarWithTimeComponent<D> extends NbCalendarComponent<D> imple
this.dateChange.emit(this.dateService.today());
}

/**
* We don't show seconds with twelve hours format
* */
showSeconds(): boolean {
return this.withSeconds && !this.twelveHoursFormat;
return this.withSeconds && !this.singleColumn;
}

isLarge(): boolean {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,9 +32,10 @@ import { NB_DATE_SERVICE_OPTIONS } from './datepicker.directive';
template: '',
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class NbDateTimePickerComponent<D> extends NbBasePickerComponent<D, D, NbCalendarWithTimeComponent<D>>
implements OnInit {

export class NbDateTimePickerComponent<D>
extends NbBasePickerComponent<D, D, NbCalendarWithTimeComponent<D>>
implements OnInit
{
protected pickerClass: Type<NbCalendarWithTimeComponent<D>> = NbCalendarWithTimeComponent;

get value(): any {
Expand Down Expand Up @@ -78,6 +79,19 @@ export class NbDateTimePickerComponent<D> extends NbBasePickerComponent<D, D, Nb
_twelveHoursFormat: boolean;
static ngAcceptInputType_twelveHoursFormat: NbBooleanInput;

/**
* Defines should show am/pm label if twelveHoursFormat enabled.
* */
@Input()
get showAmPmLabel(): boolean {
return this._showAmPmLabel;
}
set showAmPmLabel(value: boolean) {
this._showAmPmLabel = convertToBoolProperty(value);
}
protected _showAmPmLabel: boolean = true;
static ngAcceptInputType_showAmPmLabel: NbBooleanInput;

/**
* Show seconds in timepicker.
* Ignored when singleColumn is true.
Expand Down Expand Up @@ -112,14 +126,16 @@ export class NbDateTimePickerComponent<D> extends NbBasePickerComponent<D, D, Nb
return this.valueChange as EventEmitter<D>;
}

constructor(@Inject(NB_DOCUMENT) document,
positionBuilder: NbPositionBuilderService,
triggerStrategyBuilder: NbTriggerStrategyBuilderService,
overlay: NbOverlayService,
cfr: ComponentFactoryResolver,
dateService: NbDateService<D>,
@Optional() @Inject(NB_DATE_SERVICE_OPTIONS) dateServiceOptions,
protected calendarWithTimeModelService: NbCalendarTimeModelService<D>) {
constructor(
@Inject(NB_DOCUMENT) document,
positionBuilder: NbPositionBuilderService,
triggerStrategyBuilder: NbTriggerStrategyBuilderService,
overlay: NbOverlayService,
cfr: ComponentFactoryResolver,
dateService: NbDateService<D>,
@Optional() @Inject(NB_DATE_SERVICE_OPTIONS) dateServiceOptions,
protected calendarWithTimeModelService: NbCalendarTimeModelService<D>,
) {
super(document, positionBuilder, triggerStrategyBuilder, overlay, cfr, dateService, dateServiceOptions);
}

Expand All @@ -131,6 +147,7 @@ export class NbDateTimePickerComponent<D> extends NbBasePickerComponent<D, D, Nb
protected patchWithInputs() {
this.picker.singleColumn = this.singleColumn;
this.picker.twelveHoursFormat = this.twelveHoursFormat;
this.picker.showAmPmLabel = this.showAmPmLabel;
this.picker.withSeconds = this.withSeconds;
this.picker.step = this.step;
this.picker.title = this.title;
Expand All @@ -141,8 +158,10 @@ export class NbDateTimePickerComponent<D> extends NbBasePickerComponent<D, D, Nb
if (this.twelveHoursFormat) {
this.picker.timeFormat = this.dateService.getTwelveHoursFormat();
} else {
this.picker.timeFormat = this.withSeconds ? this.dateService.getTwentyFourHoursFormatWithSeconds() :
this.dateService.getTwentyFourHoursFormat();
this.picker.timeFormat =
this.withSeconds && !this.singleColumn
? this.dateService.getTwentyFourHoursFormatWithSeconds()
: this.dateService.getTwentyFourHoursFormat();
}
super.patchWithInputs();

Expand All @@ -169,4 +188,3 @@ export class NbDateTimePickerComponent<D> extends NbBasePickerComponent<D, D, Nb
}
}
}

12 changes: 10 additions & 2 deletions src/framework/theme/components/datepicker/datepicker.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,8 @@ export abstract class NbBasePicker<D, T, P> extends NbDatepicker<T, D> {
* */
abstract showWeekNumber: boolean;

readonly formatChanged$: Subject<void> = new Subject();

/**
* Calendar component class that has to be instantiated inside overlay.
* */
Expand Down Expand Up @@ -489,8 +491,14 @@ export class NbBasePickerComponent<D, T, P> extends NbBasePicker<D, T, P> implem
}

ngOnChanges(changes: SimpleChanges) {
if (changes.format && !changes.format.isFirstChange()) {
this.checkFormat();
if (changes.format) {
if (!changes.format.isFirstChange()) {
this.checkFormat();
}
this.formatChanged$.next();
}
if (this.picker) {
this.patchWithInputs();
}
}

Expand Down
24 changes: 22 additions & 2 deletions src/framework/theme/components/datepicker/datepicker.directive.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,8 @@ import {
ValidatorFn,
Validators,
} from '@angular/forms';
import { fromEvent, merge, Observable, Subject } from 'rxjs';
import { filter, map, take, takeUntil, tap } from 'rxjs/operators';
import { fromEvent, merge, Observable, Subject, Subscription } from 'rxjs';
import { distinctUntilChanged, filter, map, pairwise, startWith, take, takeUntil, tap } from 'rxjs/operators';

import { NB_DOCUMENT } from '../../theme.options';
import { NbDateService } from '../calendar-kit/services/date.service';
Expand Down Expand Up @@ -113,6 +113,8 @@ export abstract class NbDatepicker<T, D = T> {
abstract get isShown(): boolean;

abstract get blur(): Observable<void>;

abstract get formatChanged$(): Observable<void>;
}

export const NB_DATE_ADAPTER = new InjectionToken<NbDatepickerAdapter<any>>('Datepicker Adapter');
Expand Down Expand Up @@ -285,6 +287,8 @@ export class NbDatepickerDirective<D> implements OnDestroy, ControlValueAccessor
this.setupPicker();
}

protected pickerInputsChangedSubscription: Subscription | undefined;

/**
* Datepicker adapter.
* */
Expand Down Expand Up @@ -444,6 +448,22 @@ export class NbDatepickerDirective<D> implements OnDestroy, ControlValueAccessor
this.picker.value = this.datepickerAdapter.parse(this.inputValue, this.picker.format);
}

this.pickerInputsChangedSubscription?.unsubscribe();
this.pickerInputsChangedSubscription = this.picker.formatChanged$
.pipe(
map(() => this.picker.format),
startWith(this.picker.format),
distinctUntilChanged(),
pairwise(),
takeUntil(this.destroy$),
)
.subscribe(([prevFormat, nextFormat]) => {
if (this.inputValue) {
const date = this.datepickerAdapter.parse(this.inputValue, prevFormat);
this.writeInput(date);
}
});

// In case datepicker component placed after the input with datepicker directive,
// we can't read `this.picker.format` on first change detection run,
// since it's not bound yet, so we have to wait for datepicker component initialization.
Expand Down
38 changes: 24 additions & 14 deletions src/framework/theme/components/timepicker/timepicker.component.html
Original file line number Diff line number Diff line change
@@ -1,6 +1,4 @@
<nb-card *nbPortal
[class.supports-scrollbar-theming]="!isFirefox()"
class="nb-timepicker-container">
<nb-card *nbPortal [class.supports-scrollbar-theming]="!isFirefox()" class="nb-timepicker-container">
<nb-card-header class="column-header">
<ng-container *ngIf="singleColumn; else fullTimeHeadersBlock">
<div class="header-cell">Time</div>
Expand All @@ -9,7 +7,9 @@
<div class="header-cell">{{ hoursText }}</div>
<div class="header-cell">{{ minutesText }}</div>
<div *ngIf="withSeconds" class="header-cell">{{ secondsText }}</div>
<div *ngIf="twelveHoursFormat" class="header-cell">{{ ampmText }}</div>
<div *ngIf="twelveHoursFormat" class="header-cell">
<ng-template [ngIf]="showAmPmLabel">{{ ampmText }}</ng-template>
</div>
</ng-template>
</nb-card-header>

Expand All @@ -19,11 +19,13 @@
<nb-list-item
class="list-item"
[class.selected]="isSelectedFullTimeValue(item)"
*ngFor="let item of fullTimeOptions; trackBy: trackBySingleColumnValue.bind(this)">
*ngFor="let item of fullTimeOptions; trackBy: trackBySingleColumnValue.bind(this)"
>
<nb-timepicker-cell
[value]="getFullTimeString(item)"
[selected]="isSelectedFullTimeValue(item)"
(select)="selectFullTime(item)">
(select)="selectFullTime(item)"
>
</nb-timepicker-cell>
</nb-list-item>
</nb-list>
Expand All @@ -34,47 +36,55 @@
<nb-list-item
class="list-item"
[class.selected]="isSelectedHour(item.value)"
*ngFor="let item of hoursColumnOptions; trackBy: trackByTimeValues">
*ngFor="let item of hoursColumnOptions; trackBy: trackByTimeValues"
>
<nb-timepicker-cell
[value]="item.text"
[selected]="isSelectedHour(item.value)"
(select)="setHour(item.value)">
(select)="setHour(item.value)"
>
</nb-timepicker-cell>
</nb-list-item>
</nb-list>
<nb-list class="values-list">
<nb-list-item
class="list-item"
[class.selected]="isSelectedMinute(item.value)"
*ngFor="let item of minutesColumnOptions; trackBy: trackByTimeValues">
*ngFor="let item of minutesColumnOptions; trackBy: trackByTimeValues"
>
<nb-timepicker-cell
[value]="item.text"
[selected]="isSelectedMinute(item.value)"
(select)="setMinute(item.value)">
(select)="setMinute(item.value)"
>
</nb-timepicker-cell>
</nb-list-item>
</nb-list>
<nb-list *ngIf="showSeconds()" class="values-list">
<nb-list-item
class="list-item"
[class.selected]="isSelectedSecond(item.value)"
*ngFor="let item of secondsColumnOptions; trackBy: trackByTimeValues">
*ngFor="let item of secondsColumnOptions; trackBy: trackByTimeValues"
>
<nb-timepicker-cell
[value]="item.text"
[selected]="isSelectedSecond(item.value)"
(select)="setSecond(item.value)">
(select)="setSecond(item.value)"
>
</nb-timepicker-cell>
</nb-list-item>
</nb-list>
<nb-list *ngIf="twelveHoursFormat" class="values-list">
<nb-list-item
class="list-item am-pm-item"
[class.selected]="isSelectedDayPeriod(dayPeriod)"
*ngFor="let dayPeriod of dayPeriodColumnOptions; trackBy: trackByDayPeriod">
*ngFor="let dayPeriod of dayPeriodColumnOptions; trackBy: trackByDayPeriod"
>
<nb-timepicker-cell
[value]="dayPeriod"
[selected]="isSelectedDayPeriod(dayPeriod)"
(select)="changeDayPeriod(dayPeriod)">
(select)="changeDayPeriod(dayPeriod)"
>
</nb-timepicker-cell>
</nb-list-item>
</nb-list>
Expand Down
Loading

0 comments on commit 8387967

Please sign in to comment.