Skip to content
This repository has been archived by the owner on Jun 24, 2021. It is now read-only.

Commit

Permalink
feat: datepicker module
Browse files Browse the repository at this point in the history
  • Loading branch information
matheo committed Jan 14, 2018
1 parent 98b0e5e commit 1addc29
Show file tree
Hide file tree
Showing 37 changed files with 4,970 additions and 0 deletions.
49 changes: 49 additions & 0 deletions src/datepicker/calendar-body.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
<!--
If there's not enough space in the first row, create a separate label row. We mark this row as
aria-hidden because we don't want it to be read out as one of the weeks in the month.
-->
<tr *ngIf="label && _firstRowOffset < labelMinRequiredCells" aria-hidden="true">
<td class="mat-calendar-body-label"
[attr.colspan]="numCols"
[style.paddingTop.%]="50 * cellAspectRatio / numCols"
[style.paddingBottom.%]="50 * cellAspectRatio / numCols">
{{ label }}
</td>
</tr>

<!-- Create the first row separately so we can include a special spacer cell. -->
<tr *ngFor="let row of rows; let rowIndex = index" role="row">
<!--
We mark this cell as aria-hidden so it doesn't get read out as one of the days in the week.
The aspect ratio of the table cells is maintained by setting the top and bottom padding as a
percentage of the width (a variant of the trick described here:
https://www.w3schools.com/howto/howto_css_aspect_ratio.asp).
-->
<td *ngIf="rowIndex === 0 && _firstRowOffset"
aria-hidden="true"
class="mat-calendar-body-label"
[attr.colspan]="_firstRowOffset"
[style.paddingTop.%]="50 * cellAspectRatio / numCols"
[style.paddingBottom.%]="50 * cellAspectRatio / numCols">
{{ _firstRowOffset >= labelMinRequiredCells ? label : '' }}
</td>
<td *ngFor="let item of row; let colIndex = index"
role="gridcell"
class="mat-calendar-body-cell"
[tabindex]="_isActiveCell(rowIndex, colIndex) ? 0 : -1"
[class.mat-calendar-body-disabled]="!item.enabled"
[class.mat-calendar-body-active]="_isActiveCell(rowIndex, colIndex)"
[attr.aria-label]="item.ariaLabel"
[attr.aria-disabled]="!item.enabled || null"
(click)="_cellClicked(item)"
[style.width.%]="100 / numCols"
[style.paddingTop.%]="50 * cellAspectRatio / numCols"
[style.paddingBottom.%]="50 * cellAspectRatio / numCols">
<div class="mat-calendar-body-cell-background"
[class.mat-calendar-body-selected]="selectedValue === item.value"
[class.mat-calendar-body-active]="activeValue === item.value"
[class.mat-calendar-body-today]="todayValue === item.value">
</div>
<span class="mat-calendar-body-cell-content"> item.displayValue }}</span>
</td>
</tr>
124 changes: 124 additions & 0 deletions src/datepicker/calendar-body.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
@mixin mat-calendar-body($theme) {
$primary: map-get($theme, primary);

$mat-calendar-body-label-padding-start: 5% !default;
// We don't want the label to jump around when we switch between month and year views, so we use
// the same amount of padding regardless of the number of columns. We align the header label with
// the one third mark of the first cell, this was chosen somewhat arbitrarily to make it look
// roughly like the mock. Half way is too far since the cell text is center aligned.
$mat-calendar-body-label-side-padding: 33% / 7 !default;
$mat-calendar-body-cell-min-size: 32px !default;
$mat-calendar-body-cell-content-margin: 5% !default;
$mat-calendar-body-cell-content-border-width: 1px !default;

$mat-calendar-body-min-size: 7 * $mat-calendar-body-cell-min-size !default;
$mat-calendar-body-cell-content-size: 100% - $mat-calendar-body-cell-content-margin
!default;

.mat-calendar-body {
min-width: $mat-calendar-body-min-size;
}

.mat-calendar-body-label {
height: 0;
line-height: 0;
text-align: left;
padding-left: $mat-calendar-body-label-side-padding;
padding-right: $mat-calendar-body-label-side-padding;
}

[dir='rtl'] {
.mat-calendar-body-label {
text-align: right;
}
}

.mat-calendar-body-cell {
position: relative;
height: 0;
line-height: 0;
text-align: center;
outline: none;
cursor: pointer;
}

.mat-calendar-body-cell-background {
position: absolute;

display: flex;
align-items: center;
justify-content: center;

box-sizing: border-box;
opacity: 0;

border-width: $mat-calendar-body-cell-content-border-width;
border-style: solid;

transform: scale(0);
transition: all 350ms cubic-bezier(0.23, 1, 0.32, 1) 0ms;

// Choosing a value clearly larger than the height ensures we get the correct capsule shape.
border-radius: 999px;
}

// Square background
@media all and (orientation: portrait) {
.mat-calendar-body-cell-background {
top: 50%;
left: $mat-calendar-body-cell-content-margin;
margin-top: -$mat-calendar-body-cell-content-size / 2;

padding-bottom: $mat-calendar-body-cell-content-size;
width: $mat-calendar-body-cell-content-size;
}
}
@media all and (orientation: landscape) {
.mat-calendar-body-cell-background {
top: $mat-calendar-body-cell-content-margin;
left: 50%;
margin-left: -$mat-calendar-body-cell-content-size / 3 - 2;

padding-left: $mat-calendar-body-cell-content-size / 3;
padding-right: $mat-calendar-body-cell-content-size / 3;
height: $mat-calendar-body-cell-content-size - $mat-calendar-body-cell-content-margin;
}
}

.mat-calendar-body-cell:hover:not(.mat-calendar-body-disabled) {
.mat-calendar-body-cell-background {
background: mat-color($primary, 0.5);
color: mat-color($primary, 0.5);
opacity: 1;
transform: scale(1);
}
}

.mat-calendar-body-active,
.mat-calendar-body-selected,
.mat-calendar-body-today {
opacity: 1;
transform: scale(1);
}

.mat-calendar-body-selected {
background: mat-color($primary, 0.8);
color: mat-color($primary, 0.8);
}

.mat-calendar-body-active:not(.mat-calendar-body-today) {
color: mat-color($primary, 0.5);
}

.mat-calendar-body-disabled {
cursor: default;
}

.mat-calendar-body-cell-content {
font-weight: 400;
position: relative;

// Prevents text being off-center on Android.
line-height: 1;
}
}
107 changes: 107 additions & 0 deletions src/datepicker/calendar-body.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
/**
* @license
* Copyright Google LLC All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/

import {
ChangeDetectionStrategy,
Component,
EventEmitter,
Input,
Output,
ViewEncapsulation
} from '@angular/core';

/**
* An internal class that represents the data corresponding to a single calendar cell.
* @docs-private
*/
export class MatCalendarCell {
constructor(
public value: number,
public displayValue: string,
public ariaLabel: string,
public enabled: boolean
) {}
}

/**
* An internal component used to display calendar data in a table.
* @docs-private
*/
@Component({
selector: '[mat-calendar-body]',
templateUrl: 'calendar-body.html',
// styleUrls: ['calendar-body.scss'],
host: {
class: 'mat-calendar-body'
},
encapsulation: ViewEncapsulation.None,
preserveWhitespaces: false,
changeDetection: ChangeDetectionStrategy.OnPush
})
export class MatCalendarBody {
/** The label for the table. (e.g. "Jan 2017"). */
@Input() label: string;

/** The cells to display in the table. */
@Input() rows: MatCalendarCell[][];

/** The value in the table that corresponds to today. */
@Input() todayValue: number;

/** The value in the table that is active. */
@Input() activeValue: number;

/** The value in the table that is currently selected. */
@Input() selectedValue: number;

/** The minimum number of free cells needed to fit the label in the first row. */
@Input() labelMinRequiredCells: number;

/** The number of columns in the table. */
@Input() numCols = 7;

/** Whether to allow selection of disabled cells. */
@Input() allowDisabledSelection = false;

/** The cell number of the active cell in the table. */
@Input() activeCell = 0;

/**
* The aspect ratio (width / height) to use for the cells in the table. This aspect ratio will be
* maintained even as the table resizes.
*/
@Input() cellAspectRatio = 0.5;

/** Emits when a new value is selected. */
@Output() selectedValueChange = new EventEmitter<number>();

_cellClicked(cell: MatCalendarCell): void {
if (!this.allowDisabledSelection && !cell.enabled) {
return;
}
this.selectedValueChange.emit(cell.value);
}

/** The number of blank cells to put at the beginning for the first row. */
get _firstRowOffset(): number {
return this.rows && this.rows.length && this.rows[0].length
? this.numCols - this.rows[0].length
: 0;
}

_isActiveCell(rowIndex: number, colIndex: number): boolean {
let cellNumber = rowIndex * this.numCols + colIndex;

// Account for the fact that the first row may not have as many cells.
if (rowIndex) {
cellNumber -= this._firstRowOffset;
}

return cellNumber === this.activeCell;
}
}
Loading

0 comments on commit 1addc29

Please sign in to comment.