Skip to content
This repository has been archived by the owner on Sep 11, 2024. It is now read-only.

Commit

Permalink
feat(datepicker): Add min and max limits to the timepicker/timeinput …
Browse files Browse the repository at this point in the history
…+ disable cursor on disabled dates and update readme.
  • Loading branch information
nimrod13 authored and tomheller committed Jan 18, 2021
1 parent 234094e commit 46edd75
Show file tree
Hide file tree
Showing 26 changed files with 351 additions and 34 deletions.
2 changes: 2 additions & 0 deletions apps/demos/src/app-routing.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -325,6 +325,7 @@ import {
DtExampleFilterFieldInfiniteDataDepth,
DtExampleSelectCustomValueTemplate,
DtExampleCalendarMinMax,
DtExampleTimepickerMinMax,
DtExampleDatepickerDark,
DtExampleDatepickerDefault
} from '@dynatrace/barista-examples';
Expand Down Expand Up @@ -585,6 +586,7 @@ const ROUTES: Routes = [
{ path: 'drawer-nested-example', component: DtExampleDrawerNested },
{ path: 'drawer-over-example', component: DtExampleDrawerOver },
{ path: 'calendar-min-max-example', component: DtExampleCalendarMinMax},
{ path: 'timepicker-min-max-example', component: DtExampleTimepickerMinMax},
{ path: 'datepicker-dark-example', component: DtExampleDatepickerDark},
{ path: 'datepicker-default-example', component: DtExampleDatepickerDefault},
{
Expand Down
4 changes: 4 additions & 0 deletions apps/demos/src/nav-items.ts
Original file line number Diff line number Diff line change
Expand Up @@ -453,6 +453,10 @@ export const DT_DEMOS_EXAMPLE_NAV_ITEMS = [
name: 'calendar-min-max-example',
route: '/calendar-min-max-example',
},
{
name: 'timepicker-min-max-example',
route: '/timepicker-min-max-example',
},
],
},
{
Expand Down
28 changes: 27 additions & 1 deletion apps/dev/src/datepicker/datepicker-demo.component.html
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ <h2>Calendar with min and max date</h2>
>Show Calendar Today Button
</dt-checkbox>

<div class="dt-calendar-limit-wrapper">
<div class="dt-limit-wrapper">
<div>Minimum date: {{ formattedMinDate }}</div>
<div>Maximum date: {{ formattedMaxDate }}</div>
<dt-datepicker
Expand Down Expand Up @@ -93,3 +93,29 @@ <h2>Dark Mode Timepicker</h2>
></dt-timepicker>
<dt-checkbox [(ngModel)]="isDarkTimepickerDisabled">Disabled</dt-checkbox>
</div>

<h2>Timepicker with min and max hour and minute</h2>

<dt-form-field>
<dt-label>Minimum hour for timepicker: {{ minHourTimepicker }}</dt-label>
<input dtInput type="text" [(ngModel)]="minHourTimepicker" />
</dt-form-field>
<dt-form-field>
<dt-label>Maximum hour for timepicker: {{ maxHourTimepicker }}</dt-label>
<input dtInput type="text" [(ngModel)]="maxHourTimepicker" />
</dt-form-field>
<dt-form-field>
<dt-label>Minimum minute for timepicker: {{ minMinuteTimepicker }}</dt-label>
<input dtInput type="text" [(ngModel)]="minMinuteTimepicker" />
</dt-form-field>
<dt-form-field>
<dt-label>Maximum minute for timepicker: {{ maxMinuteTimepicker }}</dt-label>
<input dtInput type="text" [(ngModel)]="maxMinuteTimepicker" />
</dt-form-field>

<dt-timepicker
[minHour]="minHourTimepicker"
[maxHour]="maxHourTimepicker"
[minMinute]="minMinuteTimepicker"
[maxMinute]="maxMinuteTimepicker"
></dt-timepicker>
10 changes: 9 additions & 1 deletion apps/dev/src/datepicker/datepicker-demo.component.scss
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,21 @@
margin-top: 10px;
}

.dt-checkbox + .dt-checkbox {
margin-left: 10px;
}

.dt-example-dark h2 {
color: white;
}

.dt-calendar-limit-wrapper {
.dt-limit-wrapper {
display: grid;
grid-template-columns: 1fr 1fr;
row-gap: 10px;
margin-bottom: 10px;
}

.dt-timepicker {
margin-top: 10px;
}
4 changes: 4 additions & 0 deletions apps/dev/src/datepicker/datepicker-demo.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,10 @@ import { DtDatePicker } from '@dynatrace/barista-components/experimental/datepic
styleUrls: ['datepicker-demo.component.scss'],
})
export class DatepickerDemo {
minHourTimepicker = '';
maxHourTimepicker = '';
minMinuteTimepicker = '';
maxMinuteTimepicker = '';
startAt = new Date(2020, 7, 31);
minDate = new Date(2020, 5, 31);
maxDate = new Date(2020, 11, 31);
Expand Down
4 changes: 4 additions & 0 deletions libs/barista-components/experimental/datepicker/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -132,3 +132,7 @@ The following methods are on the `DtDatepicker` class:
## Calendar with limited date range
<ba-live-example name="DtExampleCalendarMinMax" fullwidth></ba-live-example>
## Timepicker with limited hour or minutes ranges
<ba-live-example name="DtExampleTimepickerMinMax" fullwidth></ba-live-example>
7 changes: 4 additions & 3 deletions libs/barista-components/experimental/datepicker/barista.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"title": "Datepicker",
"description": "ToDo",
"title": "Datepicker (experimental)",
"description": "Datepicker with or without an embedded timepicker for selecting a date with the option of adding a time",
"postid": "datepicker",
"identifier": "Da",
"category": "components",
Expand Down Expand Up @@ -28,5 +28,6 @@
]
},
"properties": ["work in progress", "experimental"],
"tags": ["datepicker", "component", "angular"]
"tags": ["datepicker", "component", "angular"],
"imageUrl": "https://dt-cdn.net/images/barista-preview-datepicker-307-c5c958d02e.png"
}
Original file line number Diff line number Diff line change
Expand Up @@ -55,4 +55,5 @@

.dt-calendar-cell-disabled {
color: $disabledcolor;
cursor: not-allowed;
}
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import {
isValid,
isPastedTimeValid,
isOutsideMinMaxRange,
hasMininmumTwoDigits,
} from './util';
import { DtNativeDateAdapter } from '@dynatrace/barista-components/core';

Expand All @@ -36,6 +37,37 @@ describe('timeinput', () => {
});
});

describe('hasMininmumTwoDigits', () => {
it('should return true if a number with at least two digits is passed in', () => {
expect(hasMininmumTwoDigits(15)).toBeTruthy();
expect(hasMininmumTwoDigits(20)).toBeTruthy();
expect(hasMininmumTwoDigits(123)).toBeTruthy();
});
it('should return false if a number with less than two digits is passed in', () => {
expect(hasMininmumTwoDigits(5)).toBeFalsy();
expect(hasMininmumTwoDigits(2)).toBeFalsy();
expect(hasMininmumTwoDigits(0)).toBeFalsy();
});
it('should return true if a string representing a number with at least two digits is passed in (e.g. if there is a leading 0)', () => {
expect(hasMininmumTwoDigits('15')).toBeTruthy();
expect(hasMininmumTwoDigits('20')).toBeTruthy();
expect(hasMininmumTwoDigits('02')).toBeTruthy();
expect(hasMininmumTwoDigits('05')).toBeTruthy();
expect(hasMininmumTwoDigits('00')).toBeTruthy();
});
it('should return false if a string representing a number with less than two digits is passed in', () => {
expect(hasMininmumTwoDigits('5')).toBeFalsy();
expect(hasMininmumTwoDigits('2')).toBeFalsy();
expect(hasMininmumTwoDigits('0')).toBeFalsy();
});
it('should return false if empty values are passed in', () => {
expect(hasMininmumTwoDigits('')).toBeFalsy();
expect(hasMininmumTwoDigits(' ')).toBeFalsy();
expect(hasMininmumTwoDigits(' ')).toBeFalsy();
expect(hasMininmumTwoDigits(null)).toBeFalsy();
});
});

describe('isOutsideMinMaxRange', () => {
const dateAdapter = new DtNativeDateAdapter();
it('should return true if a date earlier than the minDate is passed in', () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
*/

import {
clamp,
DtDateAdapter,
isEmpty,
isNumberLike,
Expand Down Expand Up @@ -76,26 +77,42 @@ export function isOutsideMinMaxRange<T>(
}

/** Check if the hour value is valid. */
export function isValidHour(value: any): boolean {
return isValid(value, MIN_HOURS, MAX_HOURS);
export function isValidHour(
value: any,
min?: string | number | null,
max?: string | number | null,
): boolean {
let minHour = getParsedMinHour(min);
let maxHour = getParsedMaxHour(max);
return isValid(value, minHour, maxHour);
}

/** Check if the minute value is valid. */
export function isValidMinute(value: any): boolean {
return isValid(value, MIN_MINUTES, MAX_MINUTES);
export function isValidMinute(
value: any,
min?: string | number | null,
max?: string | number | null,
): boolean {
let minMinute = getParsedMinMinute(min);
let maxMinute = getParsedMaxMinute(max);
return isValid(value, minMinute, maxMinute);
}

/**
* Check if a value of a valid hour/minute number is in the range
* Note that if a number is passed directly in with the format 'n.0', such as 5.0, it will be truncated to 5 and validation will fail.
* However, this cannot happen with the input event, since it will be passed as a string. Also, typing '.' is prevented on keydown.
*/
export function isValid(value: any, min: number, max: number): boolean {
export function isValid(
value: any,
min: string | number,
max: string | number,
): boolean {
if (isEmpty(value) || !isNumberLike(value)) {
return false;
}

// the regex is necessary for invalidating chars like '-' or '.', as well as multiple leading 0s.
// the regex is necessary for invalidating chars like '-' or '.', as well as multiple leading zeros.
const stringifiedVal = isString(value) ? value : value.toString();
if (stringifiedVal.match(INVALID_TIME_FORMAT_REGEX)) {
return false;
Expand All @@ -105,9 +122,12 @@ export function isValid(value: any, min: number, max: number): boolean {
return parsedValue >= min && parsedValue <= max;
}

/** Check if a number has at least two digits or is null. */
export function hasMininmumTwoDigits(input: number | null): boolean {
return input !== null && input >= 10;
/** Check if a passed in value has at least two digits. */
export function hasMininmumTwoDigits(input: string | number | null): boolean {
return (
input !== null &&
(typeof input === 'string' ? input.trim().length >= 2 : input >= 10)
);
}

/**
Expand All @@ -122,3 +142,50 @@ export function valueTo2DigitString(value: number): string {
export function isPastedTimeValid(value: string): boolean {
return Boolean(value.match(HOURMIN_REGEX));
}

/** Clamp the hour with the given min and max values. */
export function clampHours(
hour: number,
min?: string | number | null,
max?: string | number | null,
): string {
const minHour = getParsedMinHour(min);
const maxHour = getParsedMaxHour(max);
return `${clamp(hour, minHour, maxHour)}`;
}

/** Clamp the minute with the given min and max values. */
export function clampMinutes(
minute: number,
min?: string | number | null,
max?: string | number | null,
): string {
const minMinute = getParsedMinMinute(min);
const maxMinute = getParsedMaxMinute(max);
return `${clamp(minute, minMinute, maxMinute)}`;
}

function getParsedMinHour(min?: string | number | null): number {
const minHour = isString(min) ? parseInt(min) : min;
return !isEmpty(minHour) && minHour >= MIN_HOURS
? Math.min(minHour, MAX_HOURS)
: MIN_HOURS;
}
function getParsedMaxHour(max?: string | number | null): number {
const maxHour = isString(max) ? parseInt(max) : max;
return !isEmpty(maxHour) && maxHour <= MAX_HOURS ? maxHour : MAX_HOURS;
}

function getParsedMinMinute(min?: string | number | null): number {
const minMinute = isString(min) ? parseInt(min) : min;
return !isEmpty(minMinute) && minMinute >= MIN_MINUTES
? Math.min(minMinute, MAX_MINUTES)
: MIN_MINUTES;
}

function getParsedMaxMinute(max?: string | number | null): number {
const maxMinute = isString(max) ? parseInt(max) : max;
return !isEmpty(maxMinute) && maxMinute <= MAX_MINUTES
? maxMinute
: MAX_MINUTES;
}
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
(keydown)="_handleKeydown($event)"
(keyup)="_onHourKeyUp($event)"
(paste)="_onTimePaste($event)"
(blur)="_onHourInputBlur()"
aria-label="Please insert the hour"
/>
</div>
Expand All @@ -45,6 +46,7 @@
[ngModel]="minute"
(input)="_handleMinuteInput($event)"
(keydown)="_handleKeydown($event)"
(blur)="_onMinuteInputBlur()"
aria-label="Please insert the minutes"
/>
</div>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,8 @@ describe('DtTimeInput', () => {
it('should switch focus from the hour to the minute input when typing in 2 digits in the hour input', fakeAsync(() => {
component.timeInput.minute = null;
component.timeInput.hour = 14;
component.timeInput._hourInput.nativeElement.value = '14';
component.timeInput._minuteInput.nativeElement.value = '';
fixture.detectChanges();

dispatchKeyboardEvent(hourEl, 'keyup', NUMPAD_ONE);
Expand All @@ -120,6 +122,8 @@ describe('DtTimeInput', () => {
it('should switch focus from the hour to the minute input when typing in 2 digits in the hour input, but the minute input only had a valid 1 digit value', fakeAsync(() => {
component.timeInput.hour = 15;
component.timeInput.minute = 3;
component.timeInput._hourInput.nativeElement.value = '15';
component.timeInput._minuteInput.nativeElement.value = '3';
fixture.detectChanges();

dispatchKeyboardEvent(hourEl, 'keyup', NUMPAD_ONE);
Expand All @@ -132,6 +136,7 @@ describe('DtTimeInput', () => {
it('should not switch focus from the hour to the minute input when typing in only one digit in the hour input', fakeAsync(() => {
component.timeInput.minute = null;
component.timeInput.hour = 1;
component.timeInput._hourInput.nativeElement.value = '1';
fixture.detectChanges();

dispatchKeyboardEvent(hourEl, 'keyup', NUMPAD_ONE);
Expand All @@ -143,6 +148,8 @@ describe('DtTimeInput', () => {

it('should not switch focus from the hour to the minute input when typing in 2 digits in the hour input, but the minute input already had a valid 2 digit value', fakeAsync(() => {
component.timeInput.hour = 15;
component.timeInput._hourInput.nativeElement.value = '15';
component.timeInput._minuteInput.nativeElement.value = '53';
fixture.detectChanges();

dispatchKeyboardEvent(hourEl, 'keyup', NUMPAD_ONE);
Expand Down
Loading

1 comment on commit 46edd75

@github-actions
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Deploy preview for design-tokens-ui ready!

✅ Preview
https://design-tokens-ui-a6xf8va6i.vercel.app

Built with commit 46edd75.
This pull request is being automatically deployed with vercel-action

Please sign in to comment.