Skip to content

Commit

Permalink
feat(admin-ui): Create cross-browser datetime picker component
Browse files Browse the repository at this point in the history
This implements the basic datetime picker. Currently does not support disabled date ranges or seconds selection.
Relates to #181
  • Loading branch information
michaelbromley committed Oct 14, 2019
1 parent 3247087 commit 78a713c
Show file tree
Hide file tree
Showing 16 changed files with 759 additions and 37 deletions.
1 change: 1 addition & 0 deletions packages/admin-ui/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@
"apollo-upload-client": "^11.0.0",
"chokidar": "^3.0.2",
"core-js": "^3.1.3",
"dayjs": "^1.8.16",
"fs-extra": "^8.1.0",
"graphql": "^14.3.1",
"graphql-tag": "^2.10.1",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,22 +42,10 @@
/>
</vdr-form-field>
<vdr-form-field [label]="'marketing.starts-at' | translate" for="startsAt">
<input
type="datetime-local"
name="startsAt"
[value]="detailForm.get('startsAt')?.value | date: 'yyyy-MM-ddTHH:mm:ss'"
(change)="updateDateTime(detailForm.get('startsAt')!, $event)"
[readonly]="!('UpdatePromotion' | hasPermission)"
/>
<vdr-datetime-picker formControlName="startsAt"></vdr-datetime-picker>
</vdr-form-field>
<vdr-form-field [label]="'marketing.ends-at' | translate" for="endsAt">
<input
type="datetime-local"
name="endsAt"
[value]="detailForm.get('endsAt')?.value | date: 'yyyy-MM-ddTHH:mm:ss'"
(change)="updateDateTime(detailForm.get('endsAt')!, $event)"
[readonly]="!('UpdatePromotion' | hasPermission)"
/>
<vdr-datetime-picker formControlName="endsAt"></vdr-datetime-picker>
</vdr-form-field>
<vdr-form-field [label]="'marketing.coupon-code' | translate" for="couponCode">
<input
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -136,14 +136,6 @@ export class PromotionDetailComponent extends BaseDetailComponent<Promotion.Frag
return this.detailForm.get(key) as FormArray;
}

// TODO: Remove this once a dedicated cross-browser datetime picker
// exists. See https://github.com/vendure-ecommerce/vendure/issues/181
updateDateTime(formControl: AbstractControl, event: Event) {
const value = (event.target as HTMLInputElement).value;
formControl.setValue(value ? new Date(value).toISOString() : null, { emitEvent: true });
formControl.parent.markAsDirty();
}

create() {
if (!this.detailForm.dirty) {
return;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -57,15 +57,14 @@
[readonly]="readonly"
/>
</clr-toggle-wrapper>
<input
<vdr-datetime-picker
*ngIf="customField.type === 'datetime'"
type="datetime-local"
[id]="customField.name"
[formControl]="formGroup.get(customField.name)"
[min]="min"
[max]="max"
[step]="step"
[id]="customField.name"
[value]="formGroup.get(customField.name).value | date: 'yyyy-MM-ddTHH:mm:ss'"
(change)="updateDateTime(formGroup.get(customField.name), $event)"
[readonly]="readonly"
/>
>

</vdr-datetime-picker>
</ng-template>
Original file line number Diff line number Diff line change
Expand Up @@ -140,12 +140,6 @@ export class CustomFieldControlComponent implements OnInit, OnDestroy, AfterView
}
}

updateDateTime(formControl: FormControl, event: Event) {
const value = (event.target as HTMLInputElement).value;
formControl.setValue(value ? new Date(value).toISOString() : null, { emitEvent: true });
formControl.parent.markAsDirty();
}

getLabel(defaultLabel: string, label?: LocalizedString[] | null): string {
if (label) {
const match = label.find(l => l.languageCode === this.uiLanguageCode);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import { DayOfWeek } from '@vendure/admin-ui/src/app/shared/components/datetime-picker/types';

import { _ } from '../../../core/providers/i18n/mark-for-extraction';

export const dayOfWeekIndex: { [day in DayOfWeek]: number } = {
sun: 0,
mon: 1,
tue: 2,
wed: 3,
thu: 4,
fri: 5,
sat: 6,
};

export const weekDayNames = [
_('datetime.weekday-su'),
_('datetime.weekday-mo'),
_('datetime.weekday-tu'),
_('datetime.weekday-we'),
_('datetime.weekday-th'),
_('datetime.weekday-fr'),
_('datetime.weekday-sa'),
];
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
<div class="input-wrapper">
<input
readonly
[ngModel]="selected$ | async | date: 'medium'"
class="selected-datetime"
(keydown.enter)="dropdownComponent.toggleOpen()"
(keydown.space)="dropdownComponent.toggleOpen()"
#datetimeInput
/>
<button class="clear-value-button btn" [class.visible]="!disabled && !readonly && (selected$ | async)" (click)="clearValue()">
<clr-icon shape="times"></clr-icon>
</button>
</div>
<vdr-dropdown #dropdownComponent>
<button class="btn btn-outline calendar-button" vdrDropdownTrigger [disabled]="readonly || disabled">
<clr-icon shape="calendar"></clr-icon>
</button>
<vdr-dropdown-menu>
<div class="datetime-picker" *ngIf="current$ | async as currentView" (keydown.escape)="closeDatepicker()">
<div class="controls">
<div class="selects">
<div class="month-select">
<select
clrSelect
name="month"
[ngModel]="currentView.month"
(change)="setMonth($event)"
>
<option [value]="1">{{ 'datetime.month-jan' | translate }}</option>
<option [value]="2">{{ 'datetime.month-feb' | translate }}</option>
<option [value]="3">{{ 'datetime.month-mar' | translate }}</option>
<option [value]="4">{{ 'datetime.month-apr' | translate }}</option>
<option [value]="5">{{ 'datetime.month-may' | translate }}</option>
<option [value]="6">{{ 'datetime.month-jun' | translate }}</option>
<option [value]="7">{{ 'datetime.month-jul' | translate }}</option>
<option [value]="8">{{ 'datetime.month-aug' | translate }}</option>
<option [value]="9">{{ 'datetime.month-sep' | translate }}</option>
<option [value]="10">{{ 'datetime.month-oct' | translate }}</option>
<option [value]="11">{{ 'datetime.month-nov' | translate }}</option>
<option [value]="12">{{ 'datetime.month-dec' | translate }}</option>
</select>
</div>
<div class="year-select">
<select
clrSelect
name="month"
[ngModel]="currentView.year"
(change)="setYear($event)"
>
<option *ngFor="let year of years" [value]="year">{{ year }}</option>
</select>
</div>
</div>
<div class="control-buttons">
<button
class="btn btn-link btn-sm"
(click)="prevMonth()"
[title]="'common.view-previous-month' | translate"
>
<clr-icon shape="caret" dir="left"></clr-icon>
</button>
<button class="btn btn-link btn-sm" (click)="selectToday()" [title]="'common.select-today' | translate">
<clr-icon shape="event"></clr-icon>
</button>
<button
class="btn btn-link btn-sm"
(click)="nextMonth()"
[title]="'common.view-next-month' | translate"
>
<clr-icon shape="caret" dir="right"></clr-icon>
</button>
</div>
</div>
<table class="calendar-table" #calendarTable tabindex="0" (keydown)="handleCalendarKeydown($event)">
<thead>
<tr>
<td *ngFor="let weekdayName of weekdays">
{{ weekdayName | translate }}
</td>
</tr>
</thead>
<tbody>
<tr *ngFor="let week of calendarView$ | async">
<td
*ngFor="let day of week"
class="day-cell"
[class.selected]="day.selected"
[class.today]="day.isToday"
[class.viewing]="day.isViewing"
[class.current-month]="day.inCurrentMonth"
[class.disabled]="day.disabled"
(keydown.enter)="selectDay(day)"
(click)="selectDay(day)"
>
{{ day.dayOfMonth }}
</td>
</tr>
</tbody>
</table>
<div class="time-picker">
<span class="flex-spacer"> {{ 'datetime.time' | translate }}: </span>
<select clrSelect name="hour" [ngModel]="selectedHours$ | async" (change)="setHour($event)">
<option *ngFor="let hour of hours" [value]="hour">{{ hour | number: '2.0-0' }}</option>
</select>
<span>:</span>
<select
clrSelect
name="hour"
[ngModel]="selectedMinutes$ | async"
(change)="setMinute($event)"
>
<option *ngFor="let minute of minutes" [value]="minute">{{
minute | number: '2.0-0'
}}</option>
</select>
</div>
</div>
</vdr-dropdown-menu>
</vdr-dropdown>
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
@import "variables";

:host {
display: flex;
width: 100%;
}

.input-wrapper {
flex: 1;
display: flex;
}

input.selected-datetime {
flex: 1;
border-top-right-radius: 0 !important;
border-bottom-right-radius: 0 !important;
border-right: none !important;
}

.clear-value-button {
margin: 0;
border-radius: 0;
border-left: none;
border-color: $color-grey-300;
background-color: white;
color: $color-grey-500;
display: none;
&.visible {
display: block;
}
}

.calendar-button {
margin: 0;
border-top-left-radius: 0;
border-bottom-left-radius: 0;
}

.datetime-picker {
margin: 0 12px;
}
table.calendar-table {
padding: 6px;
&:focus {
outline: 1px solid $color-primary-500;
box-shadow: 0 0 1px 2px $color-primary-100;
}
td {
width: 24px;
text-align: center;
border: 1px solid transparent;
user-select: none;
}
.day-cell {
background-color: $color-grey-200;
color: $color-grey-500;

cursor: pointer;
transition: background-color 0.1s;
&.current-month {
background-color: white;
color: $color-grey-800;
}
&.selected {
background-color: $color-primary-500;
color: white;
}
&.viewing:not(.selected) {
background-color: $color-primary-200;
}
&.today {
border: 1px solid $color-grey-400;
}
&:hover:not(.selected):not(.disabled) {
background-color: $color-primary-100;
}
&.disabled {
cursor: default;
color: $color-grey-300;
}
}
}
.selects {
display: flex;
justify-content: space-between;
margin-bottom: 12px;
}
.control-buttons {
display: flex;
}
.time-picker {
display: flex;
align-items: baseline;
margin-top: 12px;
}
Loading

0 comments on commit 78a713c

Please sign in to comment.