diff --git a/example/src/examples/date-time/form-data.json b/example/src/examples/date-time/form-data.json new file mode 100644 index 0000000..a0fc53b --- /dev/null +++ b/example/src/examples/date-time/form-data.json @@ -0,0 +1,4 @@ +{ + "native": {}, + "alternative": {} +} diff --git a/example/src/examples/date-time/index.ts b/example/src/examples/date-time/index.ts new file mode 100644 index 0000000..64f64bc --- /dev/null +++ b/example/src/examples/date-time/index.ts @@ -0,0 +1,10 @@ +import schema from './schema.json'; +import uiSchema from './ui-schema.json'; +import formData from './form-data.json'; + +export default { + title: 'Date & time', + schema, + uiSchema, + formData, +}; diff --git a/example/src/examples/date-time/schema.json b/example/src/examples/date-time/schema.json new file mode 100644 index 0000000..f289af5 --- /dev/null +++ b/example/src/examples/date-time/schema.json @@ -0,0 +1,36 @@ +{ + "title": "Date and time widgets", + "type": "object", + "properties": { + "native": { + "title": "Native", + "description": "May not work on some browsers, notably Firefox Desktop and IE.", + "type": "object", + "properties": { + "datetime": { + "type": "string", + "format": "date-time" + }, + "date": { + "type": "string", + "format": "date" + } + } + }, + "alternative": { + "title": "Alternative", + "description": "These work on most platforms.", + "type": "object", + "properties": { + "alt-datetime": { + "type": "string", + "format": "date-time" + }, + "alt-date": { + "type": "string", + "format": "date" + } + } + } + } +} diff --git a/example/src/examples/date-time/ui-schema.json b/example/src/examples/date-time/ui-schema.json new file mode 100644 index 0000000..faa32f0 --- /dev/null +++ b/example/src/examples/date-time/ui-schema.json @@ -0,0 +1,16 @@ +{ + "alternative": { + "alt-datetime": { + "ui:widget": "alt-datetime", + "ui:options": { + "yearsRange": [1980, 2030] + } + }, + "alt-date": { + "ui:widget": "alt-date", + "ui:options": { + "yearsRange": [1980, 2030] + } + } + } +} diff --git a/example/src/examples/index.ts b/example/src/examples/index.ts index 2e2ec62..20df608 100644 --- a/example/src/examples/index.ts +++ b/example/src/examples/index.ts @@ -1,5 +1,6 @@ import arrays from './arrays'; import budget from './budget'; +import dateTime from './date-time'; import nested from './nested'; import numbers from './numbers'; import multipleChoice from './multiple-choice'; @@ -10,6 +11,7 @@ import validation from './validation'; export default { arrays, budget, + dateTime, nested, numbers, multipleChoice, diff --git a/src/AltDateWidget/AltDateWidget.tsx b/src/AltDateWidget/AltDateWidget.tsx new file mode 100644 index 0000000..4675e26 --- /dev/null +++ b/src/AltDateWidget/AltDateWidget.tsx @@ -0,0 +1,180 @@ +import React, { Component } from 'react'; + +import Grid from '@material-ui/core/Grid'; +import Button from '@material-ui/core/Button'; + +import { + parseDateString, + toDateString, + pad, +} from 'react-jsonschema-form/lib/utils'; + +function rangeOptions(start: any, stop: any) { + let options = []; + for (let i = start; i <= stop; i++) { + options.push({ value: i, label: pad(i, 2) }); + } + return options; +} + +function readyForChange(state: any) { + return Object.keys(state).every(key => state[key] !== -1); +} + +function DateElement({ + type, + range, + value, + select, + rootId, + disabled, + readonly, + autofocus, + registry, + onBlur, +}: any) { + const id = rootId + '_' + type; + const { SelectWidget } = registry.widgets; + + return ( + select(type, value)} + onBlur={onBlur} + /> + ); +} + +class AltDateWidget extends Component { + static defaultProps = { + time: false, + disabled: false, + readonly: false, + autofocus: false, + options: { + yearsRange: [1900, new Date().getFullYear() + 2], + }, + }; + + constructor(props: any) { + super(props); + + this.state = parseDateString(props.value, props.time); + } + + componentWillReceiveProps(nextProps: any) { + this.setState(parseDateString(nextProps.value, nextProps.time)); + } + + onChange = (property: any, value: any) => { + this.setState( + { [property]: typeof value === 'undefined' ? -1 : value }, + () => { + // Only propagate to parent state if we have a complete date{time} + if (readyForChange(this.state)) { + this.props.onChange(toDateString(this.state, this.props.time)); + } + } + ); + }; + + setNow = (event: any) => { + event.preventDefault(); + const { time, disabled, readonly, onChange } = this.props; + if (disabled || readonly) { + return; + } + const nowDateObj = parseDateString(new Date().toJSON(), time); + this.setState(nowDateObj, () => onChange(toDateString(this.state, time))); + }; + + clear = (event: any) => { + event.preventDefault(); + const { time, disabled, readonly, onChange } = this.props; + if (disabled || readonly) { + return; + } + this.setState(parseDateString('', time), () => onChange(undefined)); + }; + + get dateElementProps() { + const { time, options } = this.props; + const { year, month, day, hour, minute, second } = this.state; + const data = [ + { + type: 'year', + range: options.yearsRange, + value: year, + }, + { type: 'month', range: [1, 12], value: month }, + { type: 'day', range: [1, 31], value: day }, + ]; + if (time) { + data.push( + { type: 'hour', range: [0, 23], value: hour }, + { type: 'minute', range: [0, 59], value: minute }, + { type: 'second', range: [0, 59], value: second } + ); + } + return data; + } + + render() { + const { + id, + disabled, + readonly, + autofocus, + registry, + onBlur, + options, + } = this.props; + return ( + + {this.dateElementProps.map((elemProps, i) => ( + + + + ))} + {(options.hideNowButton !== 'undefined' + ? !options.hideNowButton + : true) && ( + + + + )} + {(options.hideClearButton !== 'undefined' + ? !options.hideClearButton + : true) && ( + + + + )} + + ); + } +} + +export default AltDateWidget; diff --git a/src/AltDateWidget/index.ts b/src/AltDateWidget/index.ts new file mode 100644 index 0000000..a7981fd --- /dev/null +++ b/src/AltDateWidget/index.ts @@ -0,0 +1,2 @@ +export { default } from './AltDateWidget'; +export * from './AltDateWidget'; diff --git a/src/Widgets/Widgets.ts b/src/Widgets/Widgets.ts index bd04591..07400c4 100644 --- a/src/Widgets/Widgets.ts +++ b/src/Widgets/Widgets.ts @@ -1,3 +1,4 @@ +import AltDateWidget from '../AltDateWidget/AltDateWidget'; import CheckboxWidget from '../CheckboxWidget/CheckboxWidget'; import CheckboxesWidget from '../CheckboxesWidget/CheckboxesWidget'; import PasswordWidget from '../PasswordWidget/PasswordWidget'; @@ -9,6 +10,7 @@ import TextWidget from '../TextWidget/TextWidget'; import UpDownWidget from '../UpDownWidget/UpDownWidget'; export default { + AltDateWidget, CheckboxWidget, CheckboxesWidget, PasswordWidget, diff --git a/types/index.d.ts b/types/index.d.ts new file mode 100644 index 0000000..006b338 --- /dev/null +++ b/types/index.d.ts @@ -0,0 +1,10 @@ +declare module 'react-jsonschema-form/lib/utils' { + export function parseDateString( + dateString: string, + includeTime: boolean + ): any; + + export function toDateString(obj: any, time: boolean): any; + + export function pad(num: number, size: number): string; +}