Skip to content

Commit

Permalink
create component TimePickerV2 (#806)
Browse files Browse the repository at this point in the history
  • Loading branch information
dzonidoo authored Jul 1, 2024
1 parent 3b2778f commit 61d2999
Show file tree
Hide file tree
Showing 4 changed files with 280 additions and 1 deletion.
222 changes: 222 additions & 0 deletions app-typescript/components/TimePickerV2.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,222 @@
import * as React from 'react';
import { InputWrapper } from './Form';
import {IInputWrapper} from './Form/InputWrapper';
import {padStart, range} from 'lodash';

interface IProps extends IInputWrapper {
value: string; // ISO 8601, 13:59:01
allowSeconds?: boolean;
disabledOptions: {
hours?: Array<number>;
minutes?: Array<number>;
seconds?: Array<number>;
};
'data-test-id'?: string;
onChange(valueNext: string): void;
}

type ITimeUnit = 'hours' | 'minutes' | 'seconds';

export class TimePickerV2 extends React.PureComponent<IProps> {
private is12HourFormat: boolean;

constructor(props: IProps) {
super(props);

this.handleTimeChange = this.handleTimeChange.bind(this);
this.getCorrectedTime = this.getCorrectedTime.bind(this);
this.getOptionsForTimeUnit = this.getOptionsForTimeUnit.bind(this);
this.padValue = this.padValue.bind(this);

const hour = new Date().toLocaleTimeString([], { hour: 'numeric' });
this.is12HourFormat = hour.includes('AM') || hour.includes('PM');
}

/**
* in case initial time is not valid according to disabled options, we return first valid option
*/
private getCorrectedTime(timeUnit: ITimeUnit, timeStringArray: Array<string>): string {
const dividedValue = this.props.value.split(':');
const value = (() => {
if (timeUnit === 'hours') {
return dividedValue[0];
} else if (timeUnit === 'minutes') {
return dividedValue[1];
}

return dividedValue[2];
})();

if (!(this.props.disabledOptions[timeUnit] ?? []).includes(parseInt(value, 10)) && value != null) {
return value;
}

return timeStringArray[0];
}

private getOptionsForTimeUnit(timeUnit: ITimeUnit): Array<string> {
let format12HourArr = range(1, 13);
format12HourArr.unshift(format12HourArr.pop() as number);

const timeUnitArray = (() => {
if (timeUnit === 'hours') {
if (this.is12HourFormat) {
return format12HourArr;
} else {
return range(24);
}
} else {
return range(60);
}
})();

return timeUnitArray
.filter((item) => !(this.props.disabledOptions[timeUnit] ?? []).includes(item))
.map((value) => padStart(value.toString(), 2, '0'));
}

private handleTimeChange(index: number, newValue: string) {
let current = this.props.value.split(':');

const updated12HourValue = (() => {
if (parseInt(current[0], 10) >= 12) {
if (newValue === '12') {
return newValue;
} else {
return (parseInt(newValue, 10) + 12).toString();
}
} else {
if (newValue === '12') {
return '00';
} else {
return newValue;
}
}
})();

current[index] = this.is12HourFormat ? updated12HourValue : newValue;

this.props.onChange(current.join(':'));
}

componentDidMount(): void {
const correctedTime = [
this.getCorrectedTime('hours', this.getOptionsForTimeUnit('hours')),
':',
this.getCorrectedTime('minutes', this.getOptionsForTimeUnit('minutes')),
this.props.allowSeconds
? `:${this.getCorrectedTime('seconds', this.getOptionsForTimeUnit('seconds'))}`
: '',
].join('');

if (this.props.value !== correctedTime) {
this.props.onChange(correctedTime);
}
}

padValue(value: number) {
return padStart((value).toString(), 2, '0');
}

updatedTimeUnit() {
const timeUnitValuesArray = this.props.value.split(':');

/**
* updating the initial value from props
*/
if (this.is12HourFormat) {
if (parseInt(timeUnitValuesArray[0], 10) > 12) {
timeUnitValuesArray[0] = this.padValue(parseInt(timeUnitValuesArray[0], 10) - 12);
}
}

return timeUnitValuesArray;
}

render() {
const timeUnitValuesArray = this.updatedTimeUnit();

return (
<InputWrapper
label={this.props.label}
error={this.props.error}
invalid={this.props.error != null}
required={this.props.required}
disabled={this.props.disabled}
info={this.props.info}
inlineLabel={this.props.inlineLabel}
labelHidden={this.props.labelHidden}
tabindex={this.props.tabindex}
>
<div className='sd__input__time-picker-v2' data-test-id={this.props['data-test-id']}>
<div className='input-wrapper__time-picker-v2'>
<select
className='sd-input__select'
value={timeUnitValuesArray[0]}
onChange={({target}) => {
this.handleTimeChange(0, target.value);
}}
>
{this.getOptionsForTimeUnit('hours').map((hour) => (
<option value={hour} label={hour} key={hour} />
))}
</select>
<span className='time-picker-v2-suffix'>:</span>
</div>
<div className='input-wrapper__time-picker-v2'>
<select
className='sd-input__select'
value={timeUnitValuesArray[1]}
onChange={({target}) => {
this.handleTimeChange(1, target.value);
}}
>
{this.getOptionsForTimeUnit('minutes').map((minute) => (
<option value={minute} label={minute} key={minute} />
))}
</select>
{this.props.allowSeconds && (<span className='time-picker-v2-suffix'>:</span>)}
</div>
{this.props.allowSeconds && (
<div className='input-wrapper__time-picker-v2'>
<select
className='sd-input__select'
value={timeUnitValuesArray[2]}
onChange={({target}) => {
this.handleTimeChange(2, target.value);
}}
>
{this.getOptionsForTimeUnit('seconds').map((second) => (
<option value={second} label={second} key={second} />
))}
</select>
</div>
)}
{this.is12HourFormat && (
<div className='input-wrapper__time-picker-v2'>
<span className='time-picker-v2-suffix' />
<select
className='sd-input__select'
value={(parseInt(this.props.value.split(':')[0], 10) >= 12) ? 'PM' : 'AM'}
onChange={({target}) => {
let splitValue = this.props.value.split(':');

if (target.value === 'PM') {
splitValue[0] = this.padValue(parseInt(splitValue[0], 10) + 12);
} else {
splitValue[0] = this.padValue(parseInt(splitValue[0], 10) - 12);
}

this.props.onChange(splitValue.join(':'));
}}
>
<option value='AM' label='AM' />
<option value='PM' label='PM' />
</select>
</div>
)}
</div>
</InputWrapper>
);
}
}
1 change: 1 addition & 0 deletions app-typescript/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ export { DatePicker } from './components/DatePicker';
export { DatePickerISO } from './components/DatePicker';
export { DatePickerLocaleSettings } from './components/DatePicker';
export { TimePicker } from './components/TimePicker';
export { TimePickerV2 } from './components/TimePickerV2';
export { FormLabel } from './components/FormLabel';
export { Switch } from './components/Switch';
export { SwitchGroup } from './components/SwitchGroup';
Expand Down
14 changes: 14 additions & 0 deletions app/styles/form-elements/_inputs.scss
Original file line number Diff line number Diff line change
Expand Up @@ -1365,3 +1365,17 @@
opacity: 0;
}
}

.sd__input__time-picker-v2 {
display: flex;
}

.input-wrapper__time-picker-v2 {
display: flex;
align-items: center;
}

.time-picker-v2-suffix {
padding: 0 8px;
font-size: 1.6rem;
}
44 changes: 43 additions & 1 deletion examples/pages/components/TimePicker.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,10 @@ import * as React from 'react';
import * as Markup from '../../js/react';
import {PropsList, Prop} from '../../../app-typescript';
import {TimePicker} from '../../../app-typescript/components/TimePicker';
import {TimePickerV2} from '../../../app-typescript/components/TimePickerV2';

let minutes = Array.from(Array(60).keys());
let changedMinutes = minutes.filter((num) => num % 15 !== 0)

class TimePickerExample extends React.PureComponent<{}, {time: string}> {
constructor(props) {
Expand All @@ -27,7 +31,15 @@ class TimePickerExample extends React.PureComponent<{}, {time: string}> {
}
}

export default class TimePickerDoc extends React.Component {
export default class TimePickerDoc extends React.Component<{}, {time: string}> {
constructor(props) {
super(props);

this.state = {
time: '14:00',
};
}

render() {
return (
<section className="docs-page__container">
Expand Down Expand Up @@ -59,6 +71,36 @@ export default class TimePickerDoc extends React.Component {
`}</Markup.ReactMarkupCode>
</Markup.ReactMarkup>

<p className='docs-page__paragraph'>TimePickerV2:</p>
<Markup.ReactMarkup>
<Markup.ReactMarkupPreview>
<div className='docs-page__content-row'>
<TimePickerV2
value={this.state.time}
label='This is Label'
disabledOptions={{
minutes: changedMinutes,
}}
onChange={(time) => {
this.setState({time});
}}
/>
</div>
</Markup.ReactMarkupPreview>
<Markup.ReactMarkupCode>{`
<TimePickerV2
value={this.state.time}
label='This is Label'
disableOptions={{
minutes: changedMinutes,
}}
onChange={(time) => {
this.setState({time})
}}
/>
`}</Markup.ReactMarkupCode>
</Markup.ReactMarkup>

<h3 className='docs-page__h3'>Props</h3>
<PropsList>
<Prop name='value' isRequired={true} type='string' default='/' description='Item value.' />
Expand Down

0 comments on commit 61d2999

Please sign in to comment.