diff --git a/docs/src/app/app-routes.jsx b/docs/src/app/app-routes.jsx
index 65546a67fb9252..7228c2a99e5518 100644
--- a/docs/src/app/app-routes.jsx
+++ b/docs/src/app/app-routes.jsx
@@ -31,6 +31,7 @@ var Snackbar = require('./components/pages/components/snackbar.jsx');
var Switches = require('./components/pages/components/switches.jsx');
var Tabs = require('./components/pages/components/tabs.jsx');
var TextFields = require('./components/pages/components/text-fields.jsx');
+var TimePicker = require('./components/pages/components/time-picker.jsx');
var Toolbars = require('./components/pages/components/toolbars.jsx');
@@ -70,6 +71,7 @@ var AppRoutes = (
+
diff --git a/docs/src/app/components/pages/components.jsx b/docs/src/app/components/pages/components.jsx
index 035e8855b83a73..8fee274f2a3568 100644
--- a/docs/src/app/components/pages/components.jsx
+++ b/docs/src/app/components/pages/components.jsx
@@ -20,6 +20,7 @@ class Components extends React.Component {
{ route: 'snackbar', text: 'Snackbar'},
{ route: 'tabs', text: 'Tabs'},
{ route: 'text-fields', text: 'Text Fields'},
+ { route: 'time-picker', text: 'Time Picker'},
{ route: 'toolbars', text: 'Toolbars'},
];
diff --git a/docs/src/app/components/pages/components/time-picker.jsx b/docs/src/app/components/pages/components/time-picker.jsx
new file mode 100644
index 00000000000000..df390dea1b5fca
--- /dev/null
+++ b/docs/src/app/components/pages/components/time-picker.jsx
@@ -0,0 +1,85 @@
+var React = require('react');
+var mui = require('mui');
+var TimePicker = mui.TimePicker;
+var ComponentDoc = require('../../component-doc.jsx');
+
+var TimePickerPage = React.createClass({
+
+ render: function() {
+
+ var code =
+ '//The 12hr format \n' +
+ '\n\n' +
+ '//The 24hr format \n' +
+ ' ';
+
+ var componentInfo = [
+ {
+ name: 'Props',
+ infoArray: [
+ {
+ name: 'defaultTime',
+ type: 'date object',
+ header: 'optional',
+ desc: 'This is the initial time value of the component.'
+ },
+ {
+ name: 'format',
+ type: 'one of: ampm, 24hr',
+ header: 'default: ampm',
+ desc: 'Tells the component to display the picker in ampm (12hr) format or 24hr format.'
+ }
+ ]
+ },
+ {
+ name: 'Methods',
+ infoArray: [
+ {
+ name: 'getTime',
+ header: 'DatePicker.getTime()',
+ desc: 'Returns the current time value.'
+ },
+ {
+ name: 'setTime',
+ header: 'DatePicker.setTime(t)',
+ desc: 'Sets the time value to t, where t is a date object.'
+ }
+ ]
+ }
+ ];
+
+ return (
+
+
+
+
+
+
+
+ );
+ },
+ _changeTimePicker24: function(err, t){
+ this.refs.picker24hr.setTime(t);
+ },
+ _changeTimePicker12: function(err, t){
+ this.refs.picker12hr.setTime(t);
+ }
+
+});
+
+module.exports = TimePickerPage;
\ No newline at end of file
diff --git a/src/index.js b/src/index.js
index 0945523659a682..73c727b5445209 100644
--- a/src/index.js
+++ b/src/index.js
@@ -44,6 +44,7 @@ module.exports = {
Tab: require('./tabs/tab'),
Tabs: require('./tabs/tabs'),
Toggle: require('./toggle'),
+ TimePicker: require('./time-picker'),
TextField: require('./text-field'),
Toolbar: require('./toolbar/toolbar'),
ToolbarGroup: require('./toolbar/toolbar-group'),
diff --git a/src/styles/themes/light-theme.js b/src/styles/themes/light-theme.js
index cd1e192841f511..7bbac60502aa2b 100644
--- a/src/styles/themes/light-theme.js
+++ b/src/styles/themes/light-theme.js
@@ -122,6 +122,14 @@ var LightTheme = {
backgroundColor: '#323232',
actionColor: palette.accent1Color
},
+ timePicker: {
+ color: Colors.white,
+ textColor: Colors.grey600,
+ accentColor: palette.primary1Color,
+ clockColor: Colors.black,
+ selectColor: palette.primary2Color,
+ selectTextColor: Colors.white
+ },
toggle: {
thumbOnColor: palette.primary1Color,
thumbOffColor: Colors.grey50,
diff --git a/src/time-picker/clock-button.jsx b/src/time-picker/clock-button.jsx
new file mode 100644
index 00000000000000..baf01842aff82d
--- /dev/null
+++ b/src/time-picker/clock-button.jsx
@@ -0,0 +1,96 @@
+var React = require('react');
+var StylePropable = require('../mixins/style-propable');
+var EnhancedButton = require('../enhanced-button');
+var Transitions = require('../styles/transitions');
+
+var ClockButton = React.createClass({
+
+ mixins: [StylePropable],
+
+ contextTypes: {
+ theme: React.PropTypes.object
+ },
+
+ propTypes: {
+ position: React.PropTypes.oneOf(['left', 'right'])
+ },
+
+ getDefaultProps: function () {
+ return {
+ position: "left"
+ };
+ },
+ _handleTouchTap: function(){
+
+ this.setState({
+ selected: true
+ })
+ this.props.onTouchTap();
+ },
+ getTheme: function() {
+ return this.context.theme.component.timePicker;
+ },
+ render: function() {
+
+ var {
+ className,
+ ...other} = this.props;
+
+ var styles = {
+ root: {
+ position: "absolute",
+ bottom: "65px",
+ pointerEvents: "auto",
+ height: "50px",
+ width: "50px",
+ borderRadius: "100%"
+ },
+
+ label : {
+ position: "absolute",
+ top: "17px",
+ left: "14px"
+ },
+
+ select: {
+ position: 'absolute',
+ height: 50,
+ width: 50,
+ top: "0px",
+ left: "0px",
+ opacity: 0,
+ borderRadius: '50%',
+ transform: 'scale(0)',
+ transition: Transitions.easeOut(),
+ backgroundColor: this.getTheme().accentColor,
+ },
+ };
+
+ if (this.props.selected) {
+ styles.label.color = this.getTheme().selectTextColor;
+ styles.select.opacity = 1;
+ styles.select.transform = 'scale(1)';
+ }
+
+ if( this.props.position == "right" ){
+ styles.root.right = "5px";
+ }else{
+ styles.root.left = "5px";
+ }
+
+
+
+ return (
+
+
+ {this.props.children}
+
+ );
+ }
+});
+
+module.exports = ClockButton;
\ No newline at end of file
diff --git a/src/time-picker/clock-hours.jsx b/src/time-picker/clock-hours.jsx
new file mode 100644
index 00000000000000..3cbd5a58c063fa
--- /dev/null
+++ b/src/time-picker/clock-hours.jsx
@@ -0,0 +1,178 @@
+var React = require('react');
+var StylePropable = require('../mixins/style-propable');
+var ClockNumber = require("./clock-number");
+var ClockPointer = require("./clock-pointer");
+
+function rad2deg(rad){
+ return rad * 57.29577951308232
+}
+
+var ClockHours = React.createClass({
+
+ mixins: [StylePropable],
+
+ propTypes: {
+ initialHours: React.PropTypes.number,
+ onChange: React.PropTypes.func,
+ format: React.PropTypes.oneOf(['ampm', '24hr'])
+ },
+
+ center: {x: 0, y: 0},
+ basePoint: {x: 0, y: 0},
+ isMousePressed: function(e){
+
+ if(typeof e.buttons == "undefined"){
+ return e.nativeEvent.which;
+ }
+
+ return e.buttons;
+
+ },
+ getDefaultProps: function() {
+ return {
+ initialHours: new Date().getHours(),
+ onChange: function(){},
+ format: 'ampm'
+ };
+ },
+
+ componentDidMount: function () {
+ var clockElement = this.refs.mask.getDOMNode();
+
+ this.center = {
+ x: clockElement.offsetWidth / 2,
+ y: clockElement.offsetHeight / 2,
+ };
+
+ this.basePoint = {
+ x: this.center.x,
+ y: 0
+ };
+ },
+ handleUp: function(e){
+ e.preventDefault();
+ this.setClock(e, true);
+ },
+ handleMove: function(e){
+ e.preventDefault();
+ if(this.isMousePressed(e) != 1 ) return;
+ this.setClock(e, false);
+ },
+ handleTouch: function(e){
+ e.preventDefault();
+ this.setClock(e, false);
+ },
+ setClock: function(e, finish){
+
+
+ var pos = {
+ x: e.nativeEvent.offsetX,
+ y: e.nativeEvent.offsetY
+ };
+
+ var hours = this.getHours(pos.x, pos.y);
+
+ this.props.onChange(hours, finish);
+
+ },
+ getHours: function(x, y){
+
+ var step = 30;
+ x = x - this.center.x;
+ y = y - this.center.y;
+ var cx = this.basePoint.x - this.center.x;
+ var cy = this.basePoint.y - this.center.y;
+
+ var atan = Math.atan2(cx, cy) - Math.atan2(x, y);
+
+ var deg = rad2deg(atan);
+ deg = Math.round(deg / step ) * step;
+ deg %= 360;
+
+ var value = Math.floor(deg / step) || 0;
+
+ var delta = Math.pow(x, 2) + Math.pow(y, 2);
+ var distance = Math.sqrt(delta);
+
+ value = value || 12;
+ if(this.props.format == "24hr"){
+ if(distance < 90){
+ value += 12;
+ value %= 24;
+ }
+ }else{
+ value %= 12;
+ }
+
+ return value;
+
+ },
+ _getSelected: function(){
+
+ var hour = this.props.initialHours;
+
+ if(this.props.format == "ampm"){
+ hour %= 12;
+ hour = hour || 12;
+ }
+
+ return hour;
+ },
+ _getHourNumbers: function(){
+ var style = {
+ pointerEvents: "none"
+ };
+
+ var hourSize = this.props.format == 'ampm' ? 12 : 24;
+
+ var hours = [];
+
+ for(var i = 1; i <= hourSize; i++){
+ hours.push(i % 24);
+ }
+
+ return hours.map(function(hour){
+
+ var isSelected = this._getSelected() == hour;
+ return ;
+
+ }.bind(this));
+
+ },
+
+ render: function() {
+
+ var styles = {
+ root: {
+ height: "100%",
+ width: "100%",
+ borderRadius: "100%",
+ position: "relative",
+ pointerEvents: "none",
+ boxSizing: "border-box",
+ },
+
+ hitMask: {
+ height: "100%",
+ width: "100%",
+ pointerEvents: "auto",
+ },
+
+ };
+
+
+ var hours = this._getSelected();
+ var numbers = this._getHourNumbers();
+
+ return (
+
+
+ {numbers}
+
+
+ );
+ }
+});
+
+module.exports = ClockHours;
+
\ No newline at end of file
diff --git a/src/time-picker/clock-minutes.jsx b/src/time-picker/clock-minutes.jsx
new file mode 100644
index 00000000000000..e03f8582459144
--- /dev/null
+++ b/src/time-picker/clock-minutes.jsx
@@ -0,0 +1,161 @@
+var React = require('react');
+var StylePropable = require('../mixins/style-propable');
+
+var ClockNumber = require("./clock-number");
+var ClockPointer = require("./clock-pointer");
+
+function rad2deg(rad){
+ return rad * 57.29577951308232
+}
+
+var ClockMinutes = React.createClass({
+
+ mixins: [StylePropable],
+
+ contextTypes: {
+ theme: React.PropTypes.object
+ },
+
+ propTypes: {
+ initialMinutes: React.PropTypes.number,
+ onChange: React.PropTypes.func
+ },
+
+ center: {x: 0, y: 0},
+ basePoint: {x: 0, y: 0},
+ isMousePressed: function(e){
+
+ if(typeof e.buttons == "undefined"){
+ return e.nativeEvent.which;
+ }
+ return e.buttons;
+
+ },
+ getDefaultProps: function() {
+ return {
+ initialMinutes: new Date().getMinutes(),
+ onChange: function(){}
+ };
+ },
+
+ componentDidMount: function () {
+ var clockElement = this.refs.mask.getDOMNode();
+
+ this.center = {
+ x: clockElement.offsetWidth / 2,
+ y: clockElement.offsetHeight / 2,
+ };
+
+ this.basePoint = {
+ x: this.center.x,
+ y: 0
+ };
+
+ },
+ handleUp: function(e){
+ e.preventDefault();
+ this.setClock(e, true);
+ },
+ handleMove: function(e){
+ e.preventDefault();
+ if(this.isMousePressed(e) != 1 ) return;
+ this.setClock(e, false);
+ },
+ handleTouch: function(e){
+ e.preventDefault();
+ this.setClock(e, false);
+ },
+ setClock: function(e, finish){
+
+ e.preventDefault();
+ if(this.isMousePressed(e) != 1 ) return;
+
+ var pos = {
+ x: e.nativeEvent.offsetX,
+ y: e.nativeEvent.offsetY
+ };
+
+ var minutes = this.getMinutes(pos.x, pos.y)
+
+ this.props.onChange(minutes, finish);
+
+ },
+ getMinutes: function(x, y){
+
+ var step = 6;
+ x = x - this.center.x;
+ y = y - this.center.y;
+ var cx = this.basePoint.x - this.center.x;
+ var cy = this.basePoint.y - this.center.y;
+
+ var atan = Math.atan2(cx, cy) - Math.atan2(x, y);
+
+ var deg = rad2deg(atan);
+ deg = Math.round(deg / step ) * step;
+ deg %= 360;
+
+ var value = Math.floor(deg / step) || 0;
+
+ return value;
+
+ },
+ _getMinuteNumbers: function(){
+
+ var minutes = [];
+ for(var i = 0; i < 12; i++){
+ minutes.push(i * 5);
+ }
+ var selectedMinutes = this.props.initialMinutes;
+
+
+ var hasSelected = false;
+
+ var numbers = minutes.map(function(minute){
+ var isSelected = selectedMinutes == minute;
+ if(isSelected) hasSelected = true;
+ return
;
+ }.bind(this));
+
+ return {
+ numbers: numbers,
+ hasSelected: hasSelected,
+ selected: selectedMinutes
+ }
+
+ },
+ render: function() {
+
+
+ var styles = {
+ root: {
+ height: "100%",
+ width: "100%",
+ borderRadius: "100%",
+ position: "relative",
+ pointerEvents: "none",
+ boxSizing: "border-box",
+ },
+
+ hitMask: {
+ height: "100%",
+ width: "100%",
+ pointerEvents: "auto",
+ },
+
+ };
+
+ var minutes = this._getMinuteNumbers();
+
+
+ return (
+
+
+ {minutes.numbers}
+
+
+ );
+ }
+});
+
+module.exports = ClockMinutes;
+
\ No newline at end of file
diff --git a/src/time-picker/clock-number.jsx b/src/time-picker/clock-number.jsx
new file mode 100644
index 00000000000000..9b33209993737c
--- /dev/null
+++ b/src/time-picker/clock-number.jsx
@@ -0,0 +1,116 @@
+var React = require('react');
+var StylePropable = require('../mixins/style-propable');
+
+var ClockNumber = React.createClass({
+
+ mixins: [StylePropable],
+
+ contextTypes: {
+ theme: React.PropTypes.object
+ },
+
+ propTypes: {
+ value: React.PropTypes.number,
+ type: React.PropTypes.oneOf(['hour', 'minute']),
+ onSelected: React.PropTypes.func,
+ isSelected: React.PropTypes.bool
+ },
+ getDefaultProps: function() {
+ return {
+ value: 0,
+ type: 'minute',
+ isSelected: false
+ };
+ },
+ getTheme: function() {
+ return this.context.theme.component.timePicker;
+ },
+ render: function() {
+
+ var pos = this.props.value;
+
+ var inner = false;
+
+ if(this.props.type == "hour"){
+ inner = pos < 1 || pos > 12;
+ pos %= 12;
+ }else{
+ pos = pos / 5;
+ }
+
+ var positions = [
+ [0, 5],
+ [54.5, 16.6],
+ [94.4, 59.5],
+ [109, 114],
+ [94.4, 168.5],
+ [54.5, 208.4],
+ [0, 223],
+ [-54.5, 208.4],
+ [-94.4, 168.5],
+ [-109, 114],
+ [-94.4, 59.5],
+ [-54.5, 19.6]
+ ];
+
+ var innerPositions = [
+ [0, 40],
+ [36.9, 49.9],
+ [64, 77],
+ [74, 114],
+ [64, 151],
+ [37, 178],
+ [0, 188],
+ [-37, 178],
+ [-64, 151],
+ [-74, 114],
+ [-64, 77],
+ [-37, 50]
+ ];
+
+ var styles = {
+ root: {
+ display: "inline-block",
+ position: "absolute",
+ width: "32px",
+ height: "32px",
+ borderRadius: "100%",
+ left: 'calc(50% - 16px)',
+ top: "10px",
+ textAlign: "center",
+ paddingTop: '5px',
+ userSelect: "none", /* Chrome all / Safari all */
+ fontSize: "1.1em",
+ pointerEvents: "none",
+ boxSizing: "border-box",
+ }
+
+ }
+
+ if(this.props.isSelected){
+ styles.root.backgroundColor = this.getTheme().accentColor;
+ styles.root.color = this.getTheme().selectTextColor;
+ }
+
+ var transformPos = positions[pos];
+
+ if(inner){
+ styles.root.width = "28px";
+ styles.root.height = "28px";
+ styles.root.left = 'calc(50% - 14px)';
+ transformPos = innerPositions[pos];
+ }
+
+ var [x, y] = transformPos;
+
+ styles.root.transform = "translate(" + x + "px, " + y + "px)";
+
+
+
+ return (
+
{this.props.value}
+ );
+ }
+});
+
+module.exports = ClockNumber;
\ No newline at end of file
diff --git a/src/time-picker/clock-pointer.jsx b/src/time-picker/clock-pointer.jsx
new file mode 100644
index 00000000000000..1ab13566a37248
--- /dev/null
+++ b/src/time-picker/clock-pointer.jsx
@@ -0,0 +1,108 @@
+var React = require('react');
+var StylePropable = require('../mixins/style-propable');
+
+var ClockPointer = React.createClass({
+
+ mixins: [StylePropable],
+
+ contextTypes: {
+ theme: React.PropTypes.object
+ },
+
+ propTypes: {
+ value: React.PropTypes.number,
+ type: React.PropTypes.oneOf(['hour', 'minute'])
+ },
+
+ getInitialState: function () {
+ return {
+ inner: this.isInner(this.props.value)
+ };
+ },
+ getDefaultProps: function() {
+ return {
+ value: null,
+ type: 'minute',
+ hasSelected: false
+ };
+ },
+ componentWillReceiveProps: function (nextProps) {
+
+ this.setState({
+ inner: this.isInner(nextProps.value)
+ });
+ },
+ isInner: function(value){
+ if(this.props.type != "hour" ) {
+ return false;
+ }
+ return value < 1 || value > 12 ;
+ },
+ getAngle: function(){
+
+ if(this.props.type == "hour"){
+ return this.calcAngle(this.props.value, 12);
+ }
+
+ return this.calcAngle(this.props.value, 60);
+
+ },
+ calcAngle: function(value, base){
+ value %= base;
+ var angle = 360 / base * value;
+ return angle;
+
+ },
+ getTheme: function() {
+ return this.context.theme.component.timePicker;
+ },
+ render: function() {
+
+ if(this.props.value == null){
+ return
;
+ }
+
+ var angle = this.getAngle();
+
+ var styles = {
+ root: {
+ height: "30%",
+ background: this.getTheme().accentColor,
+ width: "2px",
+ left: 'calc(50% - 1px)',
+ position: "absolute",
+ bottom: "50%",
+ transformOrigin: "bottom",
+ pointerEvents: "none",
+ transform: "rotateZ(" + angle + "deg)"
+ },
+ mark: {
+ background: this.getTheme().selectTextColor,
+ border: "4px solid " + this.getTheme().accentColor,
+ width: "7px",
+ height: "7px",
+ position: "absolute",
+ top: "-5px",
+ left: "-6px",
+ borderRadius: "100%",
+ }
+ };
+
+
+ if(!this.state.inner ){
+ styles.root.height = "40%";
+ };
+
+ if(this.props.hasSelected){
+ styles.mark.display = "none";
+ }
+
+ return (
+
+ );
+ }
+});
+
+module.exports = ClockPointer;
diff --git a/src/time-picker/clock.jsx b/src/time-picker/clock.jsx
new file mode 100644
index 00000000000000..2d6a959eba0499
--- /dev/null
+++ b/src/time-picker/clock.jsx
@@ -0,0 +1,180 @@
+var React = require('react');
+var StylePropable = require('../mixins/style-propable');
+
+var TimeDisplay = require("./time-display");
+var ClockButton = require("./clock-button");
+var ClockHours = require("./clock-hours");
+var ClockMinutes = require("./clock-minutes");
+var SlideInTransitionGroup = require('../transition-groups/slide-in');
+
+var Clock = React.createClass({
+
+ mixins: [StylePropable],
+
+ propTypes: {
+ initialTime: React.PropTypes.object,
+ mode: React.PropTypes.oneOf(['hour', 'minute']),
+ format: React.PropTypes.oneOf(['ampm', '24hr']),
+ isActive: React.PropTypes.bool
+ },
+
+ init: function(){
+ this.setState({
+ mode: 'hour'
+ })
+ },
+
+ getDefaultProps: function() {
+ return {
+ initialTime: new Date()
+ };
+ },
+
+ componentWillReceiveProps: function (nextProps) {
+
+ this.setState({
+ selectedTime: nextProps.initialTime
+ });
+ },
+
+ getInitialState: function() {
+
+ return {
+ selectedTime: this.props.initialTime,
+ mode: 'hour'
+ };
+ },
+
+
+ _setMode: function(mode){
+ setTimeout(function(){
+ this.setState({
+ mode: mode
+ })
+ }.bind(this), 100);
+ },
+
+ _setAffix: function(affix){
+
+ if(affix == this._getAffix()) return;
+
+ var hours = this.state.selectedTime.getHours();
+
+ if(affix == "am"){
+ this.handleChangeHours(hours - 12);
+ return;
+ }
+
+ this.handleChangeHours(hours + 12);
+ },
+
+ _getAffix: function(){
+
+ if(this.props.format != "ampm") return "";
+
+ var hours = this.state.selectedTime.getHours();
+ if(hours < 12){
+ return "am";
+ }
+
+ return "pm";
+
+ },
+
+ _getButtons: function(){
+ var buttons = [];
+ var isAM = this._getIsAM();
+
+ if(this.props.format == 'ampm'){
+ buttons = [
+
{"AM"},
+
{"PM"}
+ ];
+ }
+ return buttons;
+ },
+
+ _getIsAM: function(){
+
+ return this._getAffix() == "am";
+
+ },
+
+ render: function() {
+
+ var clock = null;
+ var buttons = this._getButtons();
+
+ var styles = {
+ root: {},
+
+ container: {
+ height: "280px",
+ padding: "10px",
+ }
+ };
+
+
+
+ if( this.state.mode == "hour"){
+ clock =
+ }else{
+ clock =
+
+ }
+
+
+ return (
+
+
+
+
+
+ {clock}
+
+ {buttons}
+
+ );
+ },
+ handleChangeHours: function(hours, finished){
+ var time = new Date(this.state.selectedTime);
+
+ time.setHours(hours);
+ this.setState({
+ selectedTime: time
+ });
+
+ if(finished){
+ setTimeout(function(){
+ this.setState({
+ mode: 'minute'
+ })
+ }.bind(this), 100);
+ }
+ },
+ handleChangeMinutes: function(minutes){
+ var time = new Date(this.state.selectedTime);
+ time.setMinutes(minutes);
+ this.setState({
+ selectedTime: time
+ });
+ },
+ getSelectedTime: function(){
+ return this.state.selectedTime;
+ }
+});
+
+module.exports = Clock;
\ No newline at end of file
diff --git a/src/time-picker/index.js b/src/time-picker/index.js
new file mode 100644
index 00000000000000..f29730378e1b9e
--- /dev/null
+++ b/src/time-picker/index.js
@@ -0,0 +1 @@
+module.exports = require('./time-picker');
\ No newline at end of file
diff --git a/src/time-picker/time-display.jsx b/src/time-picker/time-display.jsx
new file mode 100644
index 00000000000000..f390dc86fcac4d
--- /dev/null
+++ b/src/time-picker/time-display.jsx
@@ -0,0 +1,130 @@
+var React = require('react');
+var StylePropable = require('../mixins/style-propable');
+
+var SlideInTransitionGroup = require('../transition-groups/slide-in');
+
+var TimeDisplay = React.createClass({
+
+ mixins: [StylePropable],
+
+ contextTypes: {
+ theme: React.PropTypes.object
+ },
+
+ propTypes: {
+ selectedTime: React.PropTypes.object.isRequired,
+ format: React.PropTypes.oneOf(['ampm', '24hr']),
+ mode: React.PropTypes.oneOf(['hour', 'minute']),
+ affix: React.PropTypes.oneOf(['', 'pm', 'am'])
+ },
+
+ getInitialState: function() {
+ return {
+ transitionDirection: 'up'
+ };
+ },
+ getDefaultProps: function () {
+ return {
+ mode: 'hour' ,
+ affix: ''
+ };
+ },
+ componentWillReceiveProps: function(nextProps) {
+ var direction;
+
+ if (nextProps.selectedTime !== this.props.selectedTime) {
+ direction = nextProps.selectedTime > this.props.selectedTime ? 'up' : 'down';
+ this.setState({
+ transitionDirection: direction
+ });
+ }
+ },
+ sanitizeTime: function(time){
+ var hour = this.props.selectedTime.getHours();
+ var min = this.props.selectedTime.getMinutes().toString();
+
+ if(this.props.format == "ampm"){
+
+ hour %= 12;
+ hour = hour || 12;
+ }
+
+ hour = hour.toString();
+ if(hour.length < 2 ) hour = "0" + hour;
+ if(min.length < 2 ) min = "0" + min;
+
+ return [hour, min];
+ },
+ getTheme: function() {
+ return this.context.theme.component.timePicker;
+ },
+ render: function() {
+
+ var {
+ selectedTime,
+ mode,
+ ...other
+ } = this.props;
+
+ var styles = {
+ root: {
+ textAlign: "center",
+ position: "relative",
+ width: "280px",
+ height: "100%",
+ },
+
+ time: {
+ margin: "6px 0",
+ lineHeight: "58px",
+ height: "58px",
+ fontSize: "58px",
+ },
+
+ box: {
+ padding: "16px 0",
+ backgroundColor: this.getTheme().color,
+ color: this.getTheme().textColor,
+ },
+
+
+ hour: {},
+
+ minute: {}
+ }
+
+
+ var [hour, min] = this.sanitizeTime();
+
+
+ var hourClassName = "";
+ var minClassName = "";
+
+ styles[mode].color = this.getTheme().accentColor;
+
+ return (
+
+
+
+
+
+
+ {hour}
+ :
+ {min}
+
+
{this.props.affix.toUpperCase()}
+
+
+
+
+
+ );
+ }
+
+});
+
+module.exports = TimeDisplay;
+
+
+
\ No newline at end of file
diff --git a/src/time-picker/time-picker-dialog.jsx b/src/time-picker/time-picker-dialog.jsx
new file mode 100644
index 00000000000000..6365e133613c2a
--- /dev/null
+++ b/src/time-picker/time-picker-dialog.jsx
@@ -0,0 +1,127 @@
+var React = require('react');
+var StylePropable = require('../mixins/style-propable');
+var WindowListenable = require('../mixins/window-listenable');
+var KeyCode = require('../utils/key-code');
+var Clock = require('./clock');
+var DialogWindow = require('../dialog-window');
+var FlatButton = require('../flat-button');
+
+var TimePickerDialog = React.createClass({
+
+ mixins: [StylePropable, WindowListenable],
+
+ contextTypes: {
+ theme: React.PropTypes.object
+ },
+
+ propTypes: {
+ initialTime: React.PropTypes.object,
+ onAccept: React.PropTypes.func,
+ onShow: React.PropTypes.func,
+ onDismiss: React.PropTypes.func,
+ },
+
+ windowListeners: {
+ 'keyup': '_handleWindowKeyUp'
+ },
+
+
+ getTheme: function() {
+ return this.context.theme.component.timePicker;
+ },
+ render: function() {
+ var {
+ initialTime,
+ onAccept,
+ format,
+ ...other
+ } = this.props;
+
+ var styles = {
+ root: {
+ fontSize: "14px",
+ color: this.getTheme().clockColor,
+ },
+ dialogContent: {
+ width: "280px",
+ }
+ };
+
+ var actions = [
+
,
+
+ ];
+
+ return (
+
+
+
+ );
+ },
+
+ show: function() {
+ this.refs.dialogWindow.show();
+ this.refs.clock.init();
+ },
+
+ dismiss: function() {
+ this.refs.dialogWindow.dismiss();
+ },
+
+ _handleCancelTouchTap: function() {
+ this.dismiss();
+ },
+
+ _handleOKTouchTap: function() {
+ this.dismiss();
+ if (this.props.onAccept) {
+ this.props.onAccept(this.refs.clock.getSelectedTime());
+ }
+ },
+
+ _handleDialogShow: function() {
+
+ if(this.props.onShow) {
+ this.props.onShow();
+ }
+ },
+
+ _handleDialogDismiss: function() {
+
+
+ if(this.props.onDismiss) {
+ this.props.onDismiss();
+ }
+ },
+
+ _handleWindowKeyUp: function(e) {
+ if (this.refs.dialogWindow.isOpen()) {
+ switch (e.keyCode) {
+ case KeyCode.ENTER:
+ this._handleOKTouchTap();
+ break;
+ }
+ }
+ }
+
+});
+
+module.exports = TimePickerDialog;
\ No newline at end of file
diff --git a/src/time-picker/time-picker.jsx b/src/time-picker/time-picker.jsx
new file mode 100644
index 00000000000000..8dc18781a0fe22
--- /dev/null
+++ b/src/time-picker/time-picker.jsx
@@ -0,0 +1,135 @@
+var React = require('react');
+var StylePropable = require('../mixins/style-propable');
+
+var WindowListenable = require('../mixins/window-listenable');
+var DateTime = require('../utils/date-time');
+var KeyCode = require('../utils/key-code');
+var TimePickerDialog = require('./time-picker-dialog');
+var TextField = require('../text-field');
+
+var emptyTime = new Date();
+emptyTime.setHours(0);
+emptyTime.setMinutes(0);
+
+var TimePicker = React.createClass({
+
+ mixins: [StylePropable, WindowListenable],
+
+ propTypes: {
+ defaultTime: React.PropTypes.object,
+ format: React.PropTypes.oneOf(['ampm', '24hr']),
+ onFocus: React.PropTypes.func,
+ onTouchTap: React.PropTypes.func,
+ onChange: React.PropTypes.func,
+ onShow: React.PropTypes.func,
+ onDismiss: React.PropTypes.func,
+ },
+
+ windowListeners: {
+ 'keyup': '_handleWindowKeyUp'
+ },
+
+ getDefaultProps: function() {
+ return {
+ defaultTime: emptyTime,
+ format: 'ampm'
+ };
+ },
+
+ getInitialState: function() {
+ return {
+ time: this.props.defaultTime,
+ dialogTime: new Date()
+ };
+ },
+ formatTime: function(date){
+
+ var hours = date.getHours();
+ var mins = date.getMinutes();
+ var aditional = "";
+
+ if(this.props.format == "ampm"){
+ var isAM = hours < 12;
+ hours = hours % 12;
+ aditional += isAM ? " am" : " pm";
+ hours = hours || 12;
+ }
+
+ hours = hours.toString();
+ mins = mins.toString();
+
+ if(hours.length < 2) hours = "0" + hours;
+ if(mins.length < 2) mins = "0" + mins;
+
+ return hours + ":" + mins + aditional;
+ },
+ render: function() {
+ var {
+ format,
+ onFocus,
+ onTouchTap,
+ onShow,
+ onDismiss,
+ ...other
+ } = this.props;
+
+ var defaultInputValue;
+
+ if (this.props.defaultDate) {
+ defaultInputValue = this.formatTime(this.props.defaultTime);
+ }
+
+ return (
+
+
+
+
+
+ );
+ },
+
+ getTime: function() {
+ return this.state.time;
+ },
+
+ setTime: function(t) {
+ this.setState({
+ time: t
+ });
+ this.refs.input.setValue(this.formatTime(t));
+ },
+
+ _handleDialogAccept: function(t) {
+
+ this.setTime(t);
+ if (this.props.onChange) this.props.onChange(null, t);
+ },
+
+ _handleInputFocus: function(e) {
+ e.target.blur();
+ if (this.props.onFocus) this.props.onFocus(e);
+ },
+
+ _handleInputTouchTap: function(e) {
+ this.setState({
+ dialogTime: this.getTime()
+ });
+
+ this.refs.dialogWindow.show();
+ if (this.props.onTouchTap) this.props.onTouchTap(e);
+ }
+
+});
+
+module.exports = TimePicker;