--- /dev/null
+History
+=======
+
+---
+
+0.1.0 / 2015-11-12
+------------------
+
+`new` [#305](https://github.com/ant-design/ant-design/issues/305#issuecomment-147027817) release 0.1.0 ([@wuzhao](https://github.com/wuzhao)\)
--- /dev/null
+@prefixClass: rc-timepicker;
+
+.@{prefixClass} {
+ box-sizing: border-box;
+ * {
+ box-sizing: border-box;
+ }
+}
+
+@font-face {
+ font-family: 'anticon';
+ src: url('//at.alicdn.com/t/font_1434092639_4910953.eot');
+ /* IE9*/
+ src: url('//at.alicdn.com/t/font_1434092639_4910953.eot?#iefix') format('embedded-opentype'), /* IE6-IE8 */ url('//at.alicdn.com/t/font_1434092639_4910953.woff') format('woff'), /* chrome、firefox */ url('//at.alicdn.com/t/font_1434092639_4910953.ttf') format('truetype'), /* chrome、firefox、opera、Safari, Android, iOS 4.2+*/ url('//at.alicdn.com/t/font_1434092639_4910953.svg#iconfont') format('svg');
+ /* iOS 4.1- */
+}
+
+@import "./index/Picker";
+@import "./index/TimePanel";
+@import "./index/Header";
+@import "./index/Combobox";
+@import "./index/Select";
--- /dev/null
+.@{prefixClass} {
+ &-combobox {
+ }
+}
--- /dev/null
+.@{prefixClass} {
+ &-input {
+ margin: 0;
+ padding: 0;
+ border: 0;
+ width: 100%;
+ cursor: auto;
+ line-height: 1.5;
+ outline: 0;
+ border: 1px solid transparent;
+
+ &-wrap {
+ box-sizing: border-box;
+ position: relative;
+ padding: 6px;
+ border-bottom: 1px solid #e9e9e9;
+ }
+
+ &-invalid {
+ border-color: red;
+ }
+ }
+
+ &-clear-btn {
+ position: absolute;
+ right: 6px;
+ cursor: pointer;
+ overflow: hidden;
+ width: 20px;
+ height: 20px;
+ text-align: center;
+ line-height: 20px;
+ top: 6px;
+ margin: 0;
+ }
+
+ &-clear-btn:after {
+ content: "x";
+ font-size: 12px;
+ color: #aaa;
+ display: inline-block;
+ line-height: 1;
+ width: 20px;
+ transition: color 0.3s ease;
+ }
+
+ &-clear-btn:hover:after {
+ color: #666;
+ }
+}
+
+.narrow .@{prefixClass}-input-wrap {
+ max-width: 111px;
+}
--- /dev/null
+.@{prefixClass} {
+ &-picker {
+ }
+}
--- /dev/null
+.@{prefixClass}-select {
+ float: left;
+ overflow-y:auto;
+ font-size: 12px;
+ border: 1px solid #e9e9e9;
+ border-width: 0 1px;
+ margin-left: -1px;
+ box-sizing: border-box;
+ width: 56px;
+
+ &:first-child {
+ border-left: 0;
+ margin-left: 0;
+ }
+
+ &:last-child {
+ border-right: 0;
+ }
+
+ ul {
+ list-style: none;
+ box-sizing: border-box;
+ margin: 0;
+ padding: 0;
+ width: 100%;
+ max-height: 144px;
+ }
+
+ li {
+ list-style: none;
+ box-sizing: border-box;
+ margin: 0;
+ padding: 0 0 0 16px;
+ width: 100%;
+ height: 24px;
+ line-height: 24px;
+ text-align: left;
+ cursor: pointer;
+ user-select: none;
+
+ &.selected {
+ background: #edfaff;
+ color: #2db7f5;
+ }
+
+ &:hover {
+ background: #edfaff;
+ }
+ }
+}
--- /dev/null
+.@{prefixClass}-panel {
+ display: inline-block;
+ position: relative;
+ outline: none;
+ font-family: Arial, "Hiragino Sans GB", "Microsoft Yahei", "Microsoft Sans Serif", "WenQuanYi Micro Hei", sans-serif;
+ border: 1px solid #ccc;
+ list-style: none;
+ font-size: 12px;
+ text-align: left;
+ background-color: #fff;
+ border-radius: 3px;
+ box-shadow: 0 1px 5px #ccc;
+ background-clip: padding-box;
+ border: 1px solid #ccc;
+ line-height: 1.5;
+}
--- /dev/null
+import '../component/timepicker/assets/index.less';
+
+import React from 'react';
+import ReactDom from 'react-dom';
+import zhCn from 'gregorian-calendar/lib/locale/zh_CN';
+import GregorianCalendar from 'gregorian-calendar';
+
+import TimePicker from '../component/timepicker/src/Picker';
+import TimePanel from '../component/timepicker/src/TimePanel';
+import TimepickerLocale from '../component/timepicker/src/locale/zh_CN';
+import DateTimeFormat from 'gregorian-calendar-format';
+
+const formatter = new DateTimeFormat('HH:mm:ss');
+
+const now = new GregorianCalendar(zhCn);
+now.setTime(Date.now());
+
+const timePanel = (
+ <TimePanel
+ defaultValue={now}
+ locale={TimepickerLocale}
+ formatter={formatter}
+ minuteOptions={[0, 30]}
+ />
+);
+
+ReactDom.render(
+ <TimePicker panel={timePanel} value={now}>
+ {
+ ({value}) => {
+ return <input type="text" placeholder="请选择时间" readOnly value={value && formatter.format(value)} />;
+ }
+ }
+ </TimePicker>,
+ document.getElementById('react-content')
+);
--- /dev/null
+import TimePanel from './src/';
+export default TimePanel;
--- /dev/null
+{
+ "name": "rc-timepicker",
+ "version": "0.1.0",
+ "description": "React Timepicker",
+ "keywords": [
+ "react",
+ "react-timepicker",
+ "react-component",
+ "timepicker",
+ "ui component",
+ "ui",
+ "component"
+ ],
+ "files": [
+ "lib",
+ "assets/*.css"
+ ],
+ "main": "lib/index",
+ "homepage": "http://github.com/react-component/timepicker",
+ "author": "wuzhao.mail@gmail.com",
+ "repository": {
+ "type": "git",
+ "url": "git@github.com:react-component/timepicker.git"
+ },
+ "bugs": {
+ "url": "http://github.com/react-component/timepicker/issues"
+ },
+ "licenses": "MIT",
+ "config": {
+ "port": 8001
+ },
+ "scripts": {
+ "build": "rc-tools run build",
+ "gh-pages": "rc-tools run gh-pages",
+ "start": "rc-server",
+ "pub": "rc-tools run pub",
+ "lint": "rc-tools run lint",
+ "karma": "rc-tools run karma",
+ "saucelabs": "rc-tools run saucelabs",
+ "browser-test": "rc-tools run browser-test",
+ "browser-test-cover": "rc-tools run browser-test-cover",
+ "validate": "npm ls"
+ },
+ "devDependencies": {
+ "async": "~0.9.0",
+ "bootstrap": "~3.3.2",
+ "expect.js": "~0.3.1",
+ "jquery": "~1.11.3",
+ "pre-commit": "1.x",
+ "rc-server": "3.x",
+ "rc-tools": "4.x",
+ "react": "0.14.x",
+ "react-addons-test-utils": "~0.14.0",
+ "react-dom": "0.14.x"
+ },
+ "pre-commit": [
+ "lint"
+ ],
+ "dependencies": {
+ "gregorian-calendar": "4.x",
+ "gregorian-calendar-format": "4.x",
+ "object-assign": "4.x",
+ "rc-trigger": "1.x",
+ "rc-util": "2.x"
+ }
+}
--- /dev/null
+import React, {PropTypes} from 'react';
+import ReactDOM from 'react-dom';
+import Trigger from 'rc-trigger';
+import {createChainedFunction} from 'rc-util';
+import placements from './util/placements';
+import CommonMixin from './mixin/CommonMixin';
+
+function noop() {
+}
+
+function refFn(field, component) {
+ this[field] = component;
+}
+
+const Picker = React.createClass({
+ propTypes: {
+ prefixCls: PropTypes.string,
+ panel: PropTypes.element,
+ children: PropTypes.func,
+ disabled: PropTypes.bool,
+ value: PropTypes.object,
+ open: PropTypes.bool,
+ align: PropTypes.object,
+ placement: PropTypes.any,
+ transitionName: PropTypes.string,
+ onChange: PropTypes.func,
+ onOpen: PropTypes.func,
+ onClose: PropTypes.func,
+ },
+
+ mixins: [CommonMixin],
+
+ getDefaultProps() {
+ return {
+ open: false,
+ align: {},
+ placement: 'bottomLeft',
+ onChange: noop,
+ onOpen: noop,
+ onClose: noop,
+ };
+ },
+
+ getInitialState() {
+ this.savePanelRef = refFn.bind(this, 'panelInstance');
+ const { open, value } = this.props;
+ return { open, value };
+ },
+
+ componentWillMount() {
+ document.addEventListener('click', this.handleDocumentClick, false);
+ },
+
+ componentWillReceiveProps(nextProps) {
+ const { value, open } = nextProps;
+ if (value !== undefined) {
+ this.setState({value});
+ }
+ if (open !== undefined) {
+ this.setState({open});
+ }
+ },
+
+ componentWillUnmount() {
+ document.removeEventListener('click', this.handleDocumentClick, false);
+ },
+
+ onPanelChange(value) {
+ const props = this.props;
+ this.setState({
+ value: value,
+ });
+ props.onChange(value);
+ },
+
+ onPanelClear() {
+ this.setOpen(false, this.focus);
+ },
+
+ onVisibleChange(open) {
+ this.setOpen(open, () => {
+ if (open) {
+ ReactDOM.findDOMNode(this.panelInstance).focus();
+ }
+ });
+ },
+
+ getPanelElement() {
+ const panel = this.props.panel;
+ const extraProps = {
+ ref: this.savePanelRef,
+ defaultValue: this.state.value || panel.props.defaultValue,
+ onChange: createChainedFunction(panel.props.onChange, this.onPanelChange),
+ onClear: createChainedFunction(panel.props.onClear, this.onPanelClear),
+ };
+
+ return React.cloneElement(panel, extraProps);
+ },
+
+ setOpen(open, callback) {
+ const {onOpen, onClose} = this.props;
+ if (this.state.open !== open) {
+ this.setState({
+ open: open,
+ }, callback);
+ const event = {
+ open: open,
+ };
+ if (open) {
+ onOpen(event);
+ } else {
+ onClose(event);
+ }
+ }
+ },
+
+ handleDocumentClick(event) {
+ // hide popup when click outside
+ if (this.state.open && ReactDOM.findDOMNode(this.panelInstance).contains(event.target)) {
+ return;
+ }
+ this.setState({
+ open: false,
+ });
+ },
+
+ focus() {
+ if (!this.state.open) {
+ ReactDOM.findDOMNode(this).focus();
+ }
+ },
+
+ render() {
+ const state = this.state;
+ const props = this.props;
+ const { prefixCls, placement, align, disabled, transitionName, children } = props;
+ return (
+ <Trigger
+ prefixCls={prefixCls}
+ popup={this.getPanelElement()}
+ popupAlign={align}
+ builtinPlacements={placements}
+ popupPlacement={placement}
+ action={disabled ? [] : ['click']}
+ destroyPopupOnHide
+ popupTransitionName={transitionName}
+ popupVisible={state.open}
+ onPopupVisibleChange={this.onVisibleChange}
+ >
+ <span className={`${prefixCls}-picker`}>
+ {children(state, props)}
+ </span>
+ </Trigger>
+ );
+ },
+});
+
+export default Picker;
--- /dev/null
+import React, {PropTypes} from 'react';
+import classnames from 'classnames';
+import CommonMixin from './mixin/CommonMixin';
+import Header from './module/Header';
+import Combobox from './module/Combobox';
+
+function noop() {
+}
+
+function generateOptions(length) {
+ return Array.apply(null, {length: length}).map((item, index) => {
+ return index;
+ });
+}
+
+const TimePanel = React.createClass({
+ propTypes: {
+ prefixCls: PropTypes.string,
+ defaultValue: PropTypes.object,
+ locale: PropTypes.object,
+ placeholder: PropTypes.string,
+ formatter: PropTypes.object,
+ hourOptions: PropTypes.array,
+ minuteOptions: PropTypes.array,
+ secondOptions: PropTypes.array,
+ onChange: PropTypes.func,
+ onClear: PropTypes.func,
+ },
+
+ mixins: [CommonMixin],
+
+ getDefaultProps() {
+ return {
+ hourOptions: generateOptions(24),
+ minuteOptions: generateOptions(60),
+ secondOptions: generateOptions(60),
+ onChange: noop,
+ onClear: noop,
+ };
+ },
+
+ getInitialState() {
+ return {
+ value: this.props.defaultValue,
+ };
+ },
+
+ componentWillMount() {
+ const formatter = this.props.formatter;
+ const pattern = formatter.originalPattern;
+ if (pattern === 'HH:mm') {
+ this.showSecond = false;
+ } else if (pattern === 'mm:ss') {
+ this.showHour = false;
+ }
+ },
+
+ onChange(newValue) {
+ this.setState({ value: newValue });
+ this.props.onChange(newValue);
+ },
+
+ onClear() {
+ this.props.onClear();
+ },
+
+ getPlaceholder(placeholder) {
+ if (placeholder) {
+ return placeholder;
+ }
+
+ const { locale } = this.props;
+ if (!this.showHour) {
+ return locale.placeholdermmss;
+ } else if (!this.showSecond) {
+ return locale.placeholderHHmm;
+ }
+ return locale.placeholderHHmmss;
+ },
+
+ showHour: true,
+ showSecond: true,
+
+ render() {
+ const { locale, prefixCls, defaultValue, placeholder, hourOptions, minuteOptions, secondOptions } = this.props;
+ const value = this.state.value || defaultValue;
+ const cls = classnames({ 'narrow': !this.showHour || !this.showSecond });
+
+ return (
+ <div className={`${prefixCls}-panel ${cls}`}>
+ <Header
+ prefixCls={prefixCls}
+ gregorianTimepickerLocale={defaultValue.locale}
+ locale={locale}
+ value={value}
+ formatter={this.getFormatter()}
+ placeholder={this.getPlaceholder(placeholder)}
+ hourOptions={hourOptions}
+ minuteOptions={minuteOptions}
+ secondOptions={secondOptions}
+ onChange={this.onChange}
+ onClear={this.onClear}
+ showClear
+ />
+ <Combobox
+ prefixCls={prefixCls}
+ value={value}
+ formatter={this.getFormatter()}
+ onChange={this.onChange}
+ showHour={this.showHour}
+ showSecond={this.showSecond}
+ hourOptions={hourOptions}
+ minuteOptions={minuteOptions}
+ secondOptions={secondOptions}
+ />
+ </div>
+ );
+ },
+});
+
+export default TimePanel;
--- /dev/null
+import enUs from 'gregorian-calendar-format/lib/locale/en_US';
+
+export default {
+ placeholderHHmmss: 'HH:MM:SS',
+ placeholderHHmm: 'HH:MM',
+ placeholdermmss: 'MM:SS',
+ clear: 'Clear',
+ format: enUs,
+};
--- /dev/null
+import zhCn from 'gregorian-calendar-format/lib/locale/zh_CN';
+
+export default {
+ placeholderHHmmss: '时:分:秒',
+ placeholderHHmm: '时:分',
+ placeholdermmss: '分:秒',
+ clear: '清除',
+ format: zhCn,
+};
--- /dev/null
+import {PropTypes} from 'react';
+import enUs from '../locale/en_US';
+import {getFormatter} from '../util/index';
+
+export default {
+ propTypes: {
+ prefixCls: PropTypes.string,
+ locale: PropTypes.object,
+ },
+
+ getDefaultProps() {
+ return {
+ prefixCls: 'rc-timepicker',
+ locale: enUs,
+ };
+ },
+
+ getFormatter() {
+ const formatter = this.props.formatter;
+ const locale = this.props.locale;
+ if (formatter) {
+ if (formatter === this.lastFormatter) {
+ return this.normalFormatter;
+ }
+ this.normalFormatter = getFormatter(formatter, locale);
+ this.lastFormatter = formatter;
+ return this.normalFormatter;
+ }
+ if (!this.showSecond) {
+ if (!this.notShowSecondFormatter) {
+ this.notShowSecondFormatter = getFormatter('HH:mm', locale);
+ }
+ return this.notShowSecondFormatter;
+ }
+ if (!this.showHour) {
+ if (!this.notShowHourFormatter) {
+ this.notShowHourFormatter = getFormatter('mm:ss', locale);
+ }
+ return this.notShowHourFormatter;
+ }
+ if (!this.normalFormatter) {
+ this.normalFormatter = getFormatter('HH:mm:ss', locale);
+ }
+ return this.normalFormatter;
+ },
+};
--- /dev/null
+import React, {PropTypes} from 'react';
+import Select from './Select';
+
+const formatOption = (option) => {
+ if (option < 10) {
+ return `0${option}`;
+ }
+ return `${option}`;
+};
+
+const Combobox = React.createClass({
+ propTypes: {
+ formatter: PropTypes.object,
+ prefixCls: PropTypes.string,
+ value: PropTypes.object,
+ onChange: PropTypes.func,
+ showHour: PropTypes.bool,
+ showSecond: PropTypes.bool,
+ hourOptions: PropTypes.array,
+ minuteOptions: PropTypes.array,
+ secondOptions: PropTypes.array,
+ },
+
+ onItemChange(type, itemValue) {
+ const { value, onChange } = this.props;
+ let index = 4;
+ if (type === 'minute') {
+ index = 5;
+ } else if (type === 'second') {
+ index = 6;
+ }
+ value.fields[index] = itemValue;
+ onChange(value);
+ },
+
+ getHourSelect(hour) {
+ const { prefixCls, hourOptions, showHour } = this.props;
+ if (!showHour) {
+ return null;
+ }
+ return (
+ <Select
+ prefixCls={prefixCls}
+ options={hourOptions.map(option => formatOption(option))}
+ selectedIndex={hourOptions.indexOf(hour)}
+ type="hour"
+ onSelect={this.onItemChange}
+ />
+ );
+ },
+
+ getMinuteSelect(minute) {
+ const { prefixCls, minuteOptions } = this.props;
+ return (
+ <Select
+ prefixCls={prefixCls}
+ options={minuteOptions.map(option => formatOption(option))}
+ selectedIndex={minuteOptions.indexOf(minute)}
+ type="minute"
+ onSelect={this.onItemChange}
+ />
+ );
+ },
+
+ getSectionSelect(second) {
+ const { prefixCls, secondOptions, showSecond } = this.props;
+ if (!showSecond) {
+ return null;
+ }
+ return (
+ <Select
+ prefixCls={prefixCls}
+ options={secondOptions.map(option => formatOption(option))}
+ selectedIndex={secondOptions.indexOf(second)}
+ type="second"
+ onSelect={this.onItemChange}
+ />
+ );
+ },
+
+ render() {
+ const { prefixCls, value } = this.props;
+ const timeFields = value.fields;
+
+ return (
+ <div className={`${prefixCls}-combobox`}>
+ {this.getHourSelect(timeFields[4])}
+ {this.getMinuteSelect(timeFields[5])}
+ {this.getSectionSelect(timeFields[6])}
+ </div>
+ );
+ },
+});
+
+export default Combobox;
--- /dev/null
+import React, {PropTypes} from 'react';
+
+const Header = React.createClass({
+ propTypes: {
+ formatter: PropTypes.object,
+ prefixCls: PropTypes.string,
+ gregorianTimepickerLocale: PropTypes.object,
+ locale: PropTypes.object,
+ disabledDate: PropTypes.func,
+ placeholder: PropTypes.string,
+ value: PropTypes.object,
+ hourOptions: PropTypes.array,
+ minuteOptions: PropTypes.array,
+ secondOptions: PropTypes.array,
+ onChange: PropTypes.func,
+ onClear: PropTypes.func,
+ showClear: PropTypes.bool,
+ },
+
+ getInitialState() {
+ const value = this.props.value;
+ return {
+ str: value && this.props.formatter.format(value) || '',
+ invalid: false,
+ };
+ },
+
+ componentWillReceiveProps(nextProps) {
+ const value = this.formatValue(nextProps.value);
+ this.setState({
+ str: value && nextProps.formatter.format(value) || '',
+ invalid: false,
+ });
+ },
+
+ onInputChange(event) {
+ const str = event.target.value;
+ this.setState({
+ str,
+ });
+ let value = null;
+ const {formatter, gregorianTimepickerLocale, hourOptions, minuteOptions, secondOptions, onChange} = this.props;
+
+ if (str) {
+ const originalValue = this.props.value;
+ try {
+ value = formatter.parse(str, {
+ locale: gregorianTimepickerLocale,
+ obeyCount: true,
+ });
+ value = this.formatValue(value);
+ } catch (ex) {
+ this.setState({
+ invalid: true,
+ });
+ return;
+ }
+
+ if (value) {
+ if (
+ hourOptions.indexOf(value.fields[4]) < 0 ||
+ minuteOptions.indexOf(value.fields[5]) < 0 ||
+ secondOptions.indexOf(value.fields[6]) < 0
+ ) {
+ this.setState({
+ invalid: true,
+ });
+ return;
+ }
+
+ if (originalValue && value) {
+ if (
+ originalValue.fields[4] !== value.fields[4] ||
+ originalValue.fields[5] !== value.fields[5] ||
+ originalValue.fields[6] !== value.fields[6]
+ ) {
+ onChange(value);
+ }
+ } else if (originalValue !== value) {
+ onChange(value);
+ }
+ } else {
+ this.setState({
+ invalid: true,
+ });
+ return;
+ }
+ } else {
+ onChange(null);
+ }
+
+ this.setState({
+ invalid: false,
+ });
+ },
+
+ onClear() {
+ this.setState({str: ''});
+ this.props.onClear();
+ },
+
+ getClearButton() {
+ const { locale, prefixCls, showClear } = this.props;
+ if (!showClear) {
+ return null;
+ }
+ return <a className={`${prefixCls}-clear-btn`} role="button" title={locale.clear} onMouseDown={this.onClear} />;
+ },
+
+ getInput() {
+ const { prefixCls, placeholder } = this.props;
+ const { invalid, str } = this.state;
+ const invalidClass = invalid ? `${prefixCls}-input-invalid` : '';
+ return <input className={`${prefixCls}-input ${invalidClass}`} value={str} placeholder={placeholder} onChange={this.onInputChange} />;
+ },
+
+ formatValue(value) {
+ const newValue = this.props.value.clone();
+ if (!value) {
+ return newValue;
+ }
+ newValue.fields[4] = value.fields[4];
+ newValue.fields[5] = value.fields[5];
+ newValue.fields[6] = value.fields[6];
+ return newValue;
+ },
+
+ render() {
+ const { prefixCls } = this.props;
+ return (
+ <div className={`${prefixCls}-input-wrap`}>
+ {this.getInput()}
+ {this.getClearButton()}
+ </div>
+ );
+ },
+});
+
+export default Header;
--- /dev/null
+import React, {PropTypes} from 'react';
+import ReactDom from 'react-dom';
+import classnames from 'classnames';
+
+const scrollTo = (element, to, duration) => {
+ // jump to target if duration zero
+ if (duration <= 0) {
+ element.scrollTop = to;
+ return;
+ }
+ const difference = to - element.scrollTop;
+ const perTick = difference / duration * 10;
+
+ setTimeout(() => {
+ element.scrollTop = element.scrollTop + perTick;
+ if (element.scrollTop === to) return;
+ scrollTo(element, to, duration - 10);
+ }, 10);
+};
+
+const Select = React.createClass({
+ propTypes: {
+ prefixCls: PropTypes.string,
+ options: PropTypes.array,
+ selectedIndex: PropTypes.number,
+ type: PropTypes.string,
+ onSelect: PropTypes.func,
+ },
+
+ componentDidMount() {
+ // jump to selected option
+ this.scrollToSelected(0);
+ },
+
+ componentDidUpdate() {
+ // smooth scroll to selected option
+ this.scrollToSelected(200);
+ },
+
+ onSelect(event) {
+ // do nothing when select selected option
+ if (event.target.getAttribute('class') === 'selected') {
+ return;
+ }
+ // change combobox selection
+ const { onSelect, type } = this.props;
+ const value = parseInt(event.target.innerHTML, 10);
+ onSelect(type, value);
+ },
+
+ getOptions() {
+ const { options, selectedIndex } = this.props;
+ return options.map((item, index) => {
+ const cls = classnames({ selected: selectedIndex === index});
+ const ref = selectedIndex === index ? 'selected' : null;
+ return <li ref={ref} className={cls} key={index} onClick={this.onSelect}>{item}</li>;
+ });
+ },
+
+ scrollToSelected(duration) {
+ // move to selected item
+ const select = ReactDom.findDOMNode(this);
+ const list = ReactDom.findDOMNode(this.refs.list);
+ let index = this.props.selectedIndex - 2;
+ if (index < 0) {
+ index = 0;
+ }
+ const topOption = list.children[index];
+ const to = topOption.offsetTop - select.offsetTop;
+ scrollTo(select, to, duration);
+ },
+
+ render() {
+ if (this.props.options.length === 0) {
+ return null;
+ }
+
+ const { prefixCls } = this.props;
+
+ return (
+ <div className={`${prefixCls}-select`}>
+ <ul ref="list">{this.getOptions()}</ul>
+ </div>
+ );
+ },
+});
+
+export default Select;
--- /dev/null
+import DateTimeFormat from 'gregorian-calendar-format';
+
+export function getFormatter(format, locale) {
+ if (typeof format === 'string') {
+ return new DateTimeFormat(format, locale.format);
+ }
+ return format;
+}
--- /dev/null
+const autoAdjustOverflow = {
+ adjustX: 1,
+ adjustY: 1,
+};
+
+const targetOffset = [0, 0];
+
+const placements = {
+ topLeft: {
+ points: ['tl', 'tl'],
+ overflow: autoAdjustOverflow,
+ offset: [0, -3],
+ targetOffset,
+ },
+ topRight: {
+ points: ['tr', 'tr'],
+ overflow: autoAdjustOverflow,
+ offset: [0, -3],
+ targetOffset,
+ },
+ bottomRight: {
+ points: ['br', 'br'],
+ overflow: autoAdjustOverflow,
+ offset: [0, 3],
+ targetOffset,
+ },
+ bottomLeft: {
+ points: ['bl', 'bl'],
+ overflow: autoAdjustOverflow,
+ offset: [0, 3],
+ targetOffset,
+ },
+};
+
+export default placements;