Skip to content
This repository has been archived by the owner on May 20, 2023. It is now read-only.

add supportsMonthSelector to MaterialDatepickerComponent #365

Open
wants to merge 6 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -630,8 +630,6 @@ class DateRangeEditorComponent implements OnInit, AfterViewInit, Focusable {
desc: 'Message that explains why a date range is invalid.');
}

typedef NextPrevCallback = void Function();

class DateRangeEditorNextPrevModel implements Sequential {
final NextPrevCallback onNext;
final NextPrevCallback onPrev;
Expand Down
134 changes: 128 additions & 6 deletions angular_components/lib/material_datepicker/material_datepicker.dart
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,12 @@ import 'dart:async';
import 'dart:html';

import 'package:angular/angular.dart';
import 'package:angular_components/material_datepicker/material_month_picker.dart';
import 'package:angular_components/material_datepicker/next_prev_buttons.dart';
import 'package:angular_components/material_icon/material_icon.dart';
import 'package:angular_components/model/observable/observable.dart';
import 'package:angular_components/utils/browser/dom_service/dom_service.dart';
import 'package:angular_components/utils/showhide/showhide.dart';
import 'package:intl/intl.dart';
import 'package:quiver/time.dart';
import 'package:angular_components/button_decorator/button_decorator.dart';
Expand Down Expand Up @@ -61,6 +67,10 @@ import 'package:angular_components/utils/angular/css/css.dart';
NgFor,
NgIf,
PopupSourceDirective,
MaterialMonthPickerComponent,
ShowHideDirective,
MaterialIconComponent,
NextPrevComponent,
],
providers: [ExistingProvider(HasDisabled, MaterialDatepickerComponent)],
styleUrls: ['material_datepicker.scss.css'],
Expand Down Expand Up @@ -91,15 +101,29 @@ class MaterialDatepickerComponent
/// date which makes sense in your domain context. e.g. For apps which analyse
/// historical data, this could be the current day.
@Input()
Date maxDate;
set maxDate(Date d) {
_maxDate = d;
nextPrevModel.update(_visibleMonth, minDate, maxDate);
}

Date _maxDate;

Date get maxDate => _maxDate;

/// Dates earlier than `minDate` cannot be chosen.
///
/// Defaults to January 1, ten years ago. Set this to the earliest date which
/// makes sense in your domain context. e.g. The earliest date for which data
/// is available for analysis.
@Input()
Date minDate;
set minDate(Date d) {
_minDate = d;
nextPrevModel.update(_visibleMonth, minDate, maxDate);
}

Date _minDate;

Date get minDate => _minDate;

/// Whether to enable compact calendar styles.
@Input()
Expand Down Expand Up @@ -286,11 +310,77 @@ class MaterialDatepickerComponent
@Input()
String error;

/// Whether to display the month selector dropdown.
///
/// Defaults to true.
@Input()
bool supportsMonthSelector = true;

@ViewChild(MaterialCalendarPickerComponent)
MaterialCalendarPickerComponent calendarPicker;

@ViewChild(MaterialMonthPickerComponent)
MaterialMonthPickerComponent monthSelector;

void onMonthSelectorDropdownClicked() {
showMonthSelector = !showMonthSelector;
if (showMonthSelector) {
_domService.scheduleWrite(() {
monthSelector.scrollToYear(_visibleMonth.year);
});
}
}

set monthSelectorState(CalendarState state) {
_monthSelectorState = state;
if (state.has(state.currentSelection)) {
// A month was selected - switch back to the calendar picker and scroll
// the month into view.
showMonthSelector = false;
_monthSelectorState =
CalendarState.empty(resolution: CalendarResolution.months);
final selectedMonth = state.selection(state.currentSelection);
_domService.scheduleWrite(() {
calendarPicker.scrollToDate(selectedMonth.start);
});
}
}

CalendarState get monthSelectorState => _monthSelectorState;
CalendarState _monthSelectorState =
CalendarState.empty(resolution: CalendarResolution.months);

static final _monthFormatter = DateFormat.yMMM();
Date _visibleMonth;

String get visibleMonthName => _visibleMonthName;
String _visibleMonthName = '';

void onVisibleMonthChange(Date month) {
_visibleMonth = month;
_visibleMonthName = _monthFormatter.format(month.asUtcTime());
nextPrevModel.update(_visibleMonth, minDate, maxDate);
}

/// The model for scrolling to the next or previous month.
DatepickerNextPrevModel nextPrevModel;

bool showMonthSelector = false;

final DomService _domService;

MaterialDatepickerComponent(
HtmlElement element,
@Attribute('popupClass') String popupClass,
@Optional() @Inject(datepickerClock) Clock clock)
: popupClassName = constructEncapsulatedCss(popupClass, element.classes) {
HtmlElement element,
@Attribute('popupClass') String popupClass,
@Optional() @Inject(datepickerClock) Clock clock,
this._domService,
) : popupClassName = constructEncapsulatedCss(popupClass, element.classes) {
nextPrevModel = DatepickerNextPrevModel(onNext: () {
calendarPicker.scrollToDate(_visibleMonth.add(months: 1));
}, onPrev: () {
calendarPicker.scrollToDate(_visibleMonth.add(months: -1));
});

clock ??= Clock();

// Init minDate and maxDate to sensible defaults
Expand All @@ -299,3 +389,35 @@ class MaterialDatepickerComponent
maxDate = Date(now.year + 10, DateTime.december, 31);
}
}

class DatepickerNextPrevModel implements Sequential {
final NextPrevCallback onNext;
final NextPrevCallback onPrev;

DatepickerNextPrevModel({this.onNext, this.onPrev});

@override
ObservableReference<bool> hasNext = ObservableReference<bool>(false);

@override
ObservableReference<bool> hasPrev = ObservableReference<bool>(false);

@override
void next() => onNext();

@override
void prev() => onPrev();

void update(Date visibleMonth, Date minDate, Date maxDate) {
if (visibleMonth == null) return;

hasPrev.value = minDate != null &&
compareDatesAtResolution(
visibleMonth, minDate, CalendarResolution.months) >
0;
hasNext.value = maxDate != null &&
compareDatesAtResolution(
visibleMonth, maxDate, CalendarResolution.months) <
0;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,21 @@
</material-input>
</div>

<div class="month-selector-toolbar" *ngIf="supportsMonthSelector">
<div class="month-selector-dropdown"
buttonDecorator
keyboardOnlyFocusIndicator
(trigger)="onMonthSelectorDropdownClicked">
<span class="visible-month">{{visibleMonthName}}</span>
<material-icon icon="arrow_drop_down"
class="month-selector-dropdown-icon"
[class.flipped]="showMonthSelector"></material-icon>
</div>
<next-prev-buttons class="next-prev-range"
[model]="nextPrevModel">
</next-prev-buttons>
</div>

<div role="listbox"
class="preset-dates-wrapper"
*ngIf="presetDates.isNotEmpty">
Expand All @@ -54,15 +69,27 @@
</material-select-item>
</div>

<material-calendar-picker aria-hidden="true"
class="calendar-picker"
[ngClass]="calendarWeekRowsStyle"
[(state)]="calendar"
[minDate]="minDate"
[maxDate]="maxDate"
[compact]="compact"
mode="single-date">
</material-calendar-picker>
<div class="picker-container" [class.compact]="compact">
<material-calendar-picker aria-hidden="true"
class="calendar-picker"
[ngClass]="calendarWeekRowsStyle"
[(state)]="calendar"
[minDate]="minDate"
[maxDate]="maxDate"
[compact]="compact"
[showhide]="!showMonthSelector"
(visibleMonth)="onVisibleMonthChange"
mode="single-date">
</material-calendar-picker>
<material-month-picker class="calendar-picker month-selector"
*ngIf="supportsMonthSelector"
[showhide]="showMonthSelector"
[(state)]="monthSelectorState"
[minDate]="minDate"
[maxDate]="maxDate"
mode="single-date">
</material-month-picker>
</div>
</div>
</focus-trap>
</material-popup>
Original file line number Diff line number Diff line change
Expand Up @@ -40,8 +40,13 @@ $main-font-size: 13px;
}
}

.popup-content.compact .date-input {
padding: 0 $picker-compact-horizontal-padding;
.popup-content.compact {

.date-input,
.month-selector-toolbar {
padding: 0 $picker-compact-horizontal-padding;
}

}

.icon {
Expand Down Expand Up @@ -79,3 +84,74 @@ material-select-item {
padding-bottom: 0;
}
}

// note(lejard-h) share month selector style with date_range_editor ?
.picker-container {
@include calendar-height(7);
position: relative;
overflow: hidden;
flex-grow: 1;

&.compact {
@include calendar-compact-height(7);
}
}

.calendar-picker {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
transform: translateY(0);
transition: transform $mat-transition $mat-transition-standard;
will-change: transform;

&.acx-showhide-hide {
transform: translateY(100%);
}

&.acx-showhide-hidden {
visibility: hidden;
}
}

.month-selector {
border-top: 1px solid $mat-border-light;

&.acx-showhide-hide {
transform: translateY(-100%);
}
}

.month-selector-toolbar {
align-items: center;
color: $mat-transparent-black;
display: flex;
flex-shrink: 0;
margin-bottom: $picker-horizontal-padding;
padding: 0 $picker-horizontal-padding;
}

.month-selector-dropdown {
display: flex;
align-items: center;
margin-right: auto;
cursor: pointer;
}

.month-selector-dropdown-icon {
will-change: transform;
transition: transform $mat-transition $mat-transition-standard;

&.flipped {
transform: scaleY(-1);
}
}

.visible-month {
// TODO(google): Migrate to extended mixin mat-font-body-2
font-size: $mat-font-size-body;
font-weight: $mat-font-weight-medium;
text-transform: uppercase;
}
Original file line number Diff line number Diff line change
Expand Up @@ -129,3 +129,5 @@ class NextPrevComponent implements OnDestroy {
_modelListeners.dispose();
}
}

typedef void NextPrevCallback();