superdesk-ui-framework 5.0.2 → 5.0.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.mocharc.json +1 -1
- package/app-typescript/components/TimePicker/TimePickerPopover.spec.ts +163 -0
- package/app-typescript/components/TimePicker/TimePickerPopover.tsx +286 -0
- package/app-typescript/components/TimePicker/TimeValueHolder.tsx +36 -0
- package/app-typescript/components/{TimePicker.tsx → TimePicker/index.tsx} +6 -12
- package/dist/components/TimePicker.tsx +7 -2
- package/dist/examples.bundle.js +1091 -1006
- package/dist/superdesk-ui.bundle.js +802 -717
- package/dist/vendor.bundle.js +15 -15
- package/package.json +1 -1
- package/react/components/TimePicker/TimePickerPopover.d.ts +27 -0
- package/react/components/TimePicker/TimePickerPopover.js +231 -0
- package/react/components/TimePicker/TimeValueHolder.d.ts +13 -0
- package/react/components/TimePicker/TimeValueHolder.js +73 -0
- package/react/components/{TimePicker.d.ts → TimePicker/index.d.ts} +1 -1
- package/react/components/{TimePicker.js → TimePicker/index.js} +5 -9
- package/app-typescript/components/TimePickerPopover.tsx +0 -283
- package/react/components/TimePickerPopover.d.ts +0 -18
- package/react/components/TimePickerPopover.js +0 -222
package/.mocharc.json
CHANGED
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
import {describe, it} from 'mocha';
|
|
2
|
+
import * as assert from 'assert';
|
|
3
|
+
import {convert12HourTo24Hour, convert24HourTo12Hour, toInternalState} from './TimePickerPopover';
|
|
4
|
+
|
|
5
|
+
it('should convert all 12-hour times to 24-hour format', () => {
|
|
6
|
+
// AM times
|
|
7
|
+
assert.strictEqual(convert12HourTo24Hour(12, 'am'), 0);
|
|
8
|
+
assert.strictEqual(convert12HourTo24Hour(1, 'am'), 1);
|
|
9
|
+
assert.strictEqual(convert12HourTo24Hour(2, 'am'), 2);
|
|
10
|
+
assert.strictEqual(convert12HourTo24Hour(3, 'am'), 3);
|
|
11
|
+
assert.strictEqual(convert12HourTo24Hour(4, 'am'), 4);
|
|
12
|
+
assert.strictEqual(convert12HourTo24Hour(5, 'am'), 5);
|
|
13
|
+
assert.strictEqual(convert12HourTo24Hour(6, 'am'), 6);
|
|
14
|
+
assert.strictEqual(convert12HourTo24Hour(7, 'am'), 7);
|
|
15
|
+
assert.strictEqual(convert12HourTo24Hour(8, 'am'), 8);
|
|
16
|
+
assert.strictEqual(convert12HourTo24Hour(9, 'am'), 9);
|
|
17
|
+
assert.strictEqual(convert12HourTo24Hour(10, 'am'), 10);
|
|
18
|
+
assert.strictEqual(convert12HourTo24Hour(11, 'am'), 11);
|
|
19
|
+
|
|
20
|
+
// PM times
|
|
21
|
+
assert.strictEqual(convert12HourTo24Hour(12, 'pm'), 12);
|
|
22
|
+
assert.strictEqual(convert12HourTo24Hour(1, 'pm'), 13);
|
|
23
|
+
assert.strictEqual(convert12HourTo24Hour(2, 'pm'), 14);
|
|
24
|
+
assert.strictEqual(convert12HourTo24Hour(3, 'pm'), 15);
|
|
25
|
+
assert.strictEqual(convert12HourTo24Hour(4, 'pm'), 16);
|
|
26
|
+
assert.strictEqual(convert12HourTo24Hour(5, 'pm'), 17);
|
|
27
|
+
assert.strictEqual(convert12HourTo24Hour(6, 'pm'), 18);
|
|
28
|
+
assert.strictEqual(convert12HourTo24Hour(7, 'pm'), 19);
|
|
29
|
+
assert.strictEqual(convert12HourTo24Hour(8, 'pm'), 20);
|
|
30
|
+
assert.strictEqual(convert12HourTo24Hour(9, 'pm'), 21);
|
|
31
|
+
assert.strictEqual(convert12HourTo24Hour(10, 'pm'), 22);
|
|
32
|
+
assert.strictEqual(convert12HourTo24Hour(11, 'pm'), 23);
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
it('should convert all 24-hour times to 12-hour format', () => {
|
|
36
|
+
assert.strictEqual(convert24HourTo12Hour(0), 12);
|
|
37
|
+
assert.strictEqual(convert24HourTo12Hour(1), 1);
|
|
38
|
+
assert.strictEqual(convert24HourTo12Hour(2), 2);
|
|
39
|
+
assert.strictEqual(convert24HourTo12Hour(3), 3);
|
|
40
|
+
assert.strictEqual(convert24HourTo12Hour(4), 4);
|
|
41
|
+
assert.strictEqual(convert24HourTo12Hour(5), 5);
|
|
42
|
+
assert.strictEqual(convert24HourTo12Hour(6), 6);
|
|
43
|
+
assert.strictEqual(convert24HourTo12Hour(7), 7);
|
|
44
|
+
assert.strictEqual(convert24HourTo12Hour(8), 8);
|
|
45
|
+
assert.strictEqual(convert24HourTo12Hour(9), 9);
|
|
46
|
+
assert.strictEqual(convert24HourTo12Hour(10), 10);
|
|
47
|
+
assert.strictEqual(convert24HourTo12Hour(11), 11);
|
|
48
|
+
assert.strictEqual(convert24HourTo12Hour(12), 12);
|
|
49
|
+
assert.strictEqual(convert24HourTo12Hour(13), 1);
|
|
50
|
+
assert.strictEqual(convert24HourTo12Hour(14), 2);
|
|
51
|
+
assert.strictEqual(convert24HourTo12Hour(15), 3);
|
|
52
|
+
assert.strictEqual(convert24HourTo12Hour(16), 4);
|
|
53
|
+
assert.strictEqual(convert24HourTo12Hour(17), 5);
|
|
54
|
+
assert.strictEqual(convert24HourTo12Hour(18), 6);
|
|
55
|
+
assert.strictEqual(convert24HourTo12Hour(19), 7);
|
|
56
|
+
assert.strictEqual(convert24HourTo12Hour(20), 8);
|
|
57
|
+
assert.strictEqual(convert24HourTo12Hour(21), 9);
|
|
58
|
+
assert.strictEqual(convert24HourTo12Hour(22), 10);
|
|
59
|
+
assert.strictEqual(convert24HourTo12Hour(23), 11);
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
describe('toInternalState', () => {
|
|
63
|
+
describe('null/undefined/empty input', () => {
|
|
64
|
+
it('should return null state for null input', () => {
|
|
65
|
+
assert.deepStrictEqual(toInternalState(null), {
|
|
66
|
+
hours: null,
|
|
67
|
+
minutes: null,
|
|
68
|
+
seconds: null,
|
|
69
|
+
period: null,
|
|
70
|
+
});
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
it('should return null state for undefined input', () => {
|
|
74
|
+
assert.deepStrictEqual(toInternalState(undefined), {
|
|
75
|
+
hours: null,
|
|
76
|
+
minutes: null,
|
|
77
|
+
seconds: null,
|
|
78
|
+
period: null,
|
|
79
|
+
});
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
it('should return null state for empty string', () => {
|
|
83
|
+
assert.deepStrictEqual(toInternalState(' '), {
|
|
84
|
+
hours: null,
|
|
85
|
+
minutes: null,
|
|
86
|
+
seconds: null,
|
|
87
|
+
period: null,
|
|
88
|
+
});
|
|
89
|
+
});
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
it('should convert 00:00:00 to 12:00:00 am', () => {
|
|
93
|
+
assert.deepStrictEqual(toInternalState('00:00:00'), {
|
|
94
|
+
hours: '12',
|
|
95
|
+
minutes: '00',
|
|
96
|
+
seconds: '00',
|
|
97
|
+
period: 'am',
|
|
98
|
+
});
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
it('should convert 01:30:45 to 01:30:45 am', () => {
|
|
102
|
+
assert.deepStrictEqual(toInternalState('01:30:45'), {
|
|
103
|
+
hours: '01',
|
|
104
|
+
minutes: '30',
|
|
105
|
+
seconds: '45',
|
|
106
|
+
period: 'am',
|
|
107
|
+
});
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
it('should convert 11:59:59 to 11:59:59 am', () => {
|
|
111
|
+
assert.deepStrictEqual(toInternalState('11:59:59'), {
|
|
112
|
+
hours: '11',
|
|
113
|
+
minutes: '59',
|
|
114
|
+
seconds: '59',
|
|
115
|
+
period: 'am',
|
|
116
|
+
});
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
it('should convert 12:00:00 to 12:00:00 pm', () => {
|
|
120
|
+
assert.deepStrictEqual(toInternalState('12:00:00'), {
|
|
121
|
+
hours: '12',
|
|
122
|
+
minutes: '00',
|
|
123
|
+
seconds: '00',
|
|
124
|
+
period: 'pm',
|
|
125
|
+
});
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
it('should convert 13:30:15 to 01:30:15 pm', () => {
|
|
129
|
+
assert.deepStrictEqual(toInternalState('13:30:15'), {
|
|
130
|
+
hours: '01',
|
|
131
|
+
minutes: '30',
|
|
132
|
+
seconds: '15',
|
|
133
|
+
period: 'pm',
|
|
134
|
+
});
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
it('should convert 23:59:59 to 11:59:59 pm', () => {
|
|
138
|
+
assert.deepStrictEqual(toInternalState('23:59:59'), {
|
|
139
|
+
hours: '11',
|
|
140
|
+
minutes: '59',
|
|
141
|
+
seconds: '59',
|
|
142
|
+
period: 'pm',
|
|
143
|
+
});
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
it('should handle time without seconds and default to 00', () => {
|
|
147
|
+
assert.deepStrictEqual(toInternalState('14:30'), {
|
|
148
|
+
hours: '02',
|
|
149
|
+
minutes: '30',
|
|
150
|
+
seconds: '00',
|
|
151
|
+
period: 'pm',
|
|
152
|
+
});
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
it('should handle time without seconds in morning', () => {
|
|
156
|
+
assert.deepStrictEqual(toInternalState('09:15'), {
|
|
157
|
+
hours: '09',
|
|
158
|
+
minutes: '15',
|
|
159
|
+
seconds: '00',
|
|
160
|
+
period: 'am',
|
|
161
|
+
});
|
|
162
|
+
});
|
|
163
|
+
});
|
|
@@ -0,0 +1,286 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
import {Spacer} from '@sourcefabric/common';
|
|
3
|
+
import {ContentDivider} from '../ContentDivider';
|
|
4
|
+
import {RadioButtonGroup} from '../RadioButtonGroup';
|
|
5
|
+
import {getOptionsForTimeUnit} from '../../utils/time';
|
|
6
|
+
import {TimeValueHolder} from './TimeValueHolder';
|
|
7
|
+
|
|
8
|
+
export function convert12HourTo24Hour(hour: number, period: 'am' | 'pm'): number {
|
|
9
|
+
if (period === 'am' && hour === 12) {
|
|
10
|
+
return 0; // midnight
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
if (period === 'pm' && hour !== 12) {
|
|
14
|
+
return hour + 12; // PM and not 12
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
return hour; // For 12PM, 1-11AM
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export function convert24HourTo12Hour(hour: number) {
|
|
21
|
+
const remainder = hour % 12;
|
|
22
|
+
|
|
23
|
+
return remainder === 0 ? 12 : remainder;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function isAm(hours: number) {
|
|
27
|
+
return hours < 12;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export function toInternalState(
|
|
31
|
+
timeStr: string | undefined | null, // will always be in 24h format
|
|
32
|
+
): IState {
|
|
33
|
+
if (timeStr == null || (timeStr ?? '').trim().length < 1) {
|
|
34
|
+
return {
|
|
35
|
+
hours: null,
|
|
36
|
+
minutes: null,
|
|
37
|
+
seconds: null,
|
|
38
|
+
period: null,
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const [hours, minutes, seconds] = timeStr.split(':');
|
|
43
|
+
const secondsDefault = hours != null && minutes != null ? '00' : null;
|
|
44
|
+
|
|
45
|
+
return {
|
|
46
|
+
hours: (() => {
|
|
47
|
+
if (hours == null) {
|
|
48
|
+
return null;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
if (is12HourFormat) {
|
|
52
|
+
return convert24HourTo12Hour(parseInt(hours, 10)).toString().padStart(2, '0');
|
|
53
|
+
} else {
|
|
54
|
+
return hours;
|
|
55
|
+
}
|
|
56
|
+
})(),
|
|
57
|
+
minutes: minutes ?? null,
|
|
58
|
+
seconds: seconds ?? secondsDefault,
|
|
59
|
+
period: hours == null ? null : isAm(parseInt(hours, 10)) ? 'am' : 'pm',
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
const is12HourFormat: boolean = (() => {
|
|
64
|
+
const hour = new Date().toLocaleTimeString([]);
|
|
65
|
+
|
|
66
|
+
return hour.includes('AM') || hour.includes('PM');
|
|
67
|
+
})();
|
|
68
|
+
|
|
69
|
+
interface IProps {
|
|
70
|
+
headerTemplate?: React.ReactNode;
|
|
71
|
+
footerTemplate?: React.ReactNode;
|
|
72
|
+
allowSeconds?: boolean;
|
|
73
|
+
onChange: (nextValue: string) => void;
|
|
74
|
+
value: string | null;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// internal state is needed in order to be able to wait for all inputs to be filled before triggering `props.onChange`
|
|
78
|
+
interface IState {
|
|
79
|
+
hours: string | null;
|
|
80
|
+
minutes: string | null;
|
|
81
|
+
seconds: string | null;
|
|
82
|
+
period: 'am' | 'pm' | null;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
export class TimePickerPopover extends React.PureComponent<IProps, IState> {
|
|
86
|
+
// hour, minutes, seconds
|
|
87
|
+
private inputRefs: Array<React.RefObject<TimeValueHolder>>;
|
|
88
|
+
|
|
89
|
+
constructor(props: IProps) {
|
|
90
|
+
super(props);
|
|
91
|
+
|
|
92
|
+
this.inputRefs = [React.createRef(), React.createRef(), React.createRef()];
|
|
93
|
+
this.handleChange = this.handleChange.bind(this);
|
|
94
|
+
this.scrollToValues = this.scrollToValues.bind(this);
|
|
95
|
+
|
|
96
|
+
this.state = toInternalState(props.value);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
private handleChange(nextState: IState) {
|
|
100
|
+
this.setState(nextState, () => {
|
|
101
|
+
let timeParts: Array<string> = [];
|
|
102
|
+
|
|
103
|
+
if (this.state.hours == null) {
|
|
104
|
+
return;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
if (is12HourFormat) {
|
|
108
|
+
if (this.state.period == null) {
|
|
109
|
+
return;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
timeParts.push(
|
|
113
|
+
convert12HourTo24Hour(parseInt(this.state.hours, 10), this.state.period)
|
|
114
|
+
.toString()
|
|
115
|
+
.padStart(2, '0'),
|
|
116
|
+
);
|
|
117
|
+
} else {
|
|
118
|
+
timeParts.push(this.state.hours);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
if (this.state.minutes == null) {
|
|
122
|
+
return;
|
|
123
|
+
} else {
|
|
124
|
+
timeParts.push(this.state.minutes);
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
if (this.props.allowSeconds) {
|
|
128
|
+
if (this.state.seconds == null) {
|
|
129
|
+
return;
|
|
130
|
+
} else {
|
|
131
|
+
timeParts.push(this.state.seconds);
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
this.props.onChange(timeParts.join(':'));
|
|
136
|
+
});
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
scrollToValues() {
|
|
140
|
+
this.inputRefs.forEach((unitOfTime) => unitOfTime?.current?.scrollToValue?.());
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
componentDidMount(): void {
|
|
144
|
+
this.scrollToValues();
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
componentDidUpdate(prevProps: Readonly<IProps>): void {
|
|
148
|
+
if (this.props.value !== prevProps.value) {
|
|
149
|
+
this.setState(toInternalState(this.props.value), () => {
|
|
150
|
+
this.scrollToValues();
|
|
151
|
+
});
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
render(): React.ReactNode {
|
|
156
|
+
const styleForColumnOfUnit: React.CSSProperties = {
|
|
157
|
+
maxHeight: 190,
|
|
158
|
+
overflowY: 'auto',
|
|
159
|
+
scrollbarWidth: 'thin',
|
|
160
|
+
marginTop: 'var(--gap-1)',
|
|
161
|
+
scrollBehavior: 'smooth',
|
|
162
|
+
};
|
|
163
|
+
|
|
164
|
+
return (
|
|
165
|
+
<div className="sd-shadow--z2 radius-md">
|
|
166
|
+
<Spacer
|
|
167
|
+
v
|
|
168
|
+
gap="0"
|
|
169
|
+
style={{
|
|
170
|
+
minWidth: 200,
|
|
171
|
+
maxWidth: 'max-content',
|
|
172
|
+
backgroundColor: 'var(--color-dropdown-menu-Bg)',
|
|
173
|
+
borderRadius: 'var(--b-radius--small)',
|
|
174
|
+
}}
|
|
175
|
+
>
|
|
176
|
+
{this.props.headerTemplate && (
|
|
177
|
+
<div className="px-1-5 py-1" style={{borderBottom: '1px solid var(--color-line-x-light)'}}>
|
|
178
|
+
{this.props.headerTemplate}
|
|
179
|
+
</div>
|
|
180
|
+
)}
|
|
181
|
+
|
|
182
|
+
<Spacer
|
|
183
|
+
h
|
|
184
|
+
gap="4"
|
|
185
|
+
noWrap
|
|
186
|
+
justifyContent="center"
|
|
187
|
+
alignItems="start"
|
|
188
|
+
style={{paddingInline: 'var(--gap-1)'}}
|
|
189
|
+
>
|
|
190
|
+
<Spacer v gap="4" style={styleForColumnOfUnit} alignItems="center" noWrap>
|
|
191
|
+
{getOptionsForTimeUnit('hours', is12HourFormat).map((hour) => {
|
|
192
|
+
const isActiveHour = hour === this.state.hours;
|
|
193
|
+
|
|
194
|
+
return (
|
|
195
|
+
<TimeValueHolder
|
|
196
|
+
key={hour}
|
|
197
|
+
ref={isActiveHour ? this.inputRefs[0] : undefined}
|
|
198
|
+
onClick={() => {
|
|
199
|
+
this.handleChange({...this.state, hours: hour});
|
|
200
|
+
}}
|
|
201
|
+
isActive={isActiveHour}
|
|
202
|
+
value={hour}
|
|
203
|
+
/>
|
|
204
|
+
);
|
|
205
|
+
})}
|
|
206
|
+
</Spacer>
|
|
207
|
+
|
|
208
|
+
<ContentDivider align="center" border type="solid" orientation="vertical" margin="none" />
|
|
209
|
+
|
|
210
|
+
<Spacer v gap="4" style={styleForColumnOfUnit} alignItems="center" noWrap>
|
|
211
|
+
{getOptionsForTimeUnit('minutes', is12HourFormat).map((minute) => {
|
|
212
|
+
const isActiveMinute = minute === this.state.minutes;
|
|
213
|
+
|
|
214
|
+
return (
|
|
215
|
+
<TimeValueHolder
|
|
216
|
+
key={minute}
|
|
217
|
+
ref={isActiveMinute ? this.inputRefs[1] : undefined}
|
|
218
|
+
isActive={isActiveMinute}
|
|
219
|
+
value={minute}
|
|
220
|
+
onClick={() => {
|
|
221
|
+
this.handleChange({...this.state, minutes: minute});
|
|
222
|
+
}}
|
|
223
|
+
/>
|
|
224
|
+
);
|
|
225
|
+
})}
|
|
226
|
+
</Spacer>
|
|
227
|
+
|
|
228
|
+
{this.props.allowSeconds && (
|
|
229
|
+
<>
|
|
230
|
+
<ContentDivider
|
|
231
|
+
align="center"
|
|
232
|
+
border
|
|
233
|
+
type="solid"
|
|
234
|
+
orientation="vertical"
|
|
235
|
+
margin="none"
|
|
236
|
+
/>
|
|
237
|
+
<Spacer v gap="4" style={styleForColumnOfUnit} alignItems="center" noWrap>
|
|
238
|
+
{getOptionsForTimeUnit('seconds', is12HourFormat).map((second) => {
|
|
239
|
+
const isActiveSecond = second === this.state.seconds;
|
|
240
|
+
|
|
241
|
+
return (
|
|
242
|
+
<TimeValueHolder
|
|
243
|
+
key={second}
|
|
244
|
+
ref={isActiveSecond ? this.inputRefs[2] : undefined}
|
|
245
|
+
onClick={() => {
|
|
246
|
+
this.handleChange({...this.state, seconds: second});
|
|
247
|
+
}}
|
|
248
|
+
isActive={isActiveSecond}
|
|
249
|
+
value={second}
|
|
250
|
+
/>
|
|
251
|
+
);
|
|
252
|
+
})}
|
|
253
|
+
</Spacer>
|
|
254
|
+
</>
|
|
255
|
+
)}
|
|
256
|
+
|
|
257
|
+
{is12HourFormat && (
|
|
258
|
+
<div
|
|
259
|
+
style={{
|
|
260
|
+
marginTop: 'var(--gap-1)',
|
|
261
|
+
}}
|
|
262
|
+
>
|
|
263
|
+
<RadioButtonGroup
|
|
264
|
+
onChange={(nextValue) => {
|
|
265
|
+
this.handleChange({...this.state, period: nextValue as 'am' | 'pm'});
|
|
266
|
+
}}
|
|
267
|
+
options={[
|
|
268
|
+
{label: 'AM', value: 'am'},
|
|
269
|
+
{label: 'PM', value: 'pm'},
|
|
270
|
+
]}
|
|
271
|
+
value={this.state.period == null ? '' : this.state.period}
|
|
272
|
+
/>
|
|
273
|
+
</div>
|
|
274
|
+
)}
|
|
275
|
+
</Spacer>
|
|
276
|
+
|
|
277
|
+
{this.props.footerTemplate && (
|
|
278
|
+
<div className="px-1-5 py-1" style={{borderTop: '1px solid var(--color-line-x-light)'}}>
|
|
279
|
+
{this.props.footerTemplate}
|
|
280
|
+
</div>
|
|
281
|
+
)}
|
|
282
|
+
</Spacer>
|
|
283
|
+
</div>
|
|
284
|
+
);
|
|
285
|
+
}
|
|
286
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
import {classnames} from '@sourcefabric/common';
|
|
3
|
+
|
|
4
|
+
interface IProps {
|
|
5
|
+
isActive?: boolean;
|
|
6
|
+
value: string;
|
|
7
|
+
onClick(event: React.MouseEvent<HTMLSpanElement>): void;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export class TimeValueHolder extends React.PureComponent<IProps> {
|
|
11
|
+
private spanEl: React.RefObject<HTMLSpanElement>;
|
|
12
|
+
|
|
13
|
+
constructor(props: IProps) {
|
|
14
|
+
super(props);
|
|
15
|
+
|
|
16
|
+
this.spanEl = React.createRef();
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
public scrollToValue() {
|
|
20
|
+
this.spanEl.current?.scrollIntoView({block: 'start', behavior: 'smooth'});
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
render() {
|
|
24
|
+
return (
|
|
25
|
+
<span
|
|
26
|
+
ref={this.props.isActive ? this.spanEl : undefined}
|
|
27
|
+
onClick={this.props.onClick}
|
|
28
|
+
className={classnames('p-1 time-unit', {
|
|
29
|
+
'time-unit-highlight': this.props.isActive ?? false,
|
|
30
|
+
})}
|
|
31
|
+
>
|
|
32
|
+
{this.props.value}
|
|
33
|
+
</span>
|
|
34
|
+
);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
import * as React from 'react';
|
|
2
2
|
import nextId from 'react-id-generator';
|
|
3
3
|
import classNames from 'classnames';
|
|
4
|
-
import {InputWrapper} from '
|
|
5
|
-
import {IInputWrapper} from '
|
|
4
|
+
import {InputWrapper} from '../Form';
|
|
5
|
+
import {IInputWrapper} from '../Form/InputWrapper';
|
|
6
6
|
import {TimePickerPopover} from './TimePickerPopover';
|
|
7
|
-
import {PopupPositioner} from '
|
|
8
|
-
import {Icon} from '
|
|
9
|
-
import {IconButton} from '
|
|
7
|
+
import {PopupPositioner} from '../ShowPopup';
|
|
8
|
+
import {Icon} from '../Icon';
|
|
9
|
+
import {IconButton} from '../IconButton';
|
|
10
10
|
|
|
11
11
|
interface IProps extends IInputWrapper {
|
|
12
12
|
value: string | null; // ISO8601 time string(e.g. 16:55) or null if there's no value
|
|
@@ -120,18 +120,12 @@ export class TimePicker extends React.PureComponent<IProps, IState> {
|
|
|
120
120
|
ref={this.timeInputRef}
|
|
121
121
|
value={this.props.value ?? ''}
|
|
122
122
|
type="time"
|
|
123
|
-
onClick={(
|
|
124
|
-
// don't show default popup
|
|
125
|
-
e.preventDefault();
|
|
126
|
-
|
|
123
|
+
onClick={() => {
|
|
127
124
|
this.setState({
|
|
128
125
|
popupOpen: !this.state.popupOpen,
|
|
129
126
|
});
|
|
130
127
|
}}
|
|
131
128
|
onKeyDown={(event) => {
|
|
132
|
-
// don't show default popup
|
|
133
|
-
event.preventDefault();
|
|
134
|
-
|
|
135
129
|
if (event.key === ' ') {
|
|
136
130
|
this.setState({
|
|
137
131
|
popupOpen: !this.state.popupOpen,
|
|
@@ -36,7 +36,12 @@ class TimePickerExample extends React.PureComponent<{}, {time: string | null}> {
|
|
|
36
36
|
<Button size="small" text="In 30 min" style="hollow" onClick={() => false} />
|
|
37
37
|
<Button size="small" text="In 1 hr" style="hollow" onClick={() => false} />
|
|
38
38
|
<Button size="small" text="In 2 hr" style="hollow" onClick={() => false} />
|
|
39
|
-
<Button
|
|
39
|
+
<Button
|
|
40
|
+
size="small"
|
|
41
|
+
text="16:32"
|
|
42
|
+
style="hollow"
|
|
43
|
+
onClick={() => this.setState({time: '16:32'})}
|
|
44
|
+
/>
|
|
40
45
|
</ButtonGroup>
|
|
41
46
|
}
|
|
42
47
|
footerTemplate={<div>Footer</div>}
|
|
@@ -65,7 +70,7 @@ export default class TimePickerDoc extends React.Component<{}, {time: string}> {
|
|
|
65
70
|
render() {
|
|
66
71
|
return (
|
|
67
72
|
<section className="docs-page__container">
|
|
68
|
-
<h2 className="docs-page__h2">Time picker</h2>
|
|
73
|
+
<h2 className="docs-page__h2">Time picker 1</h2>
|
|
69
74
|
<Markup.ReactMarkupCodePreview>{`
|
|
70
75
|
<TimePicker
|
|
71
76
|
value={this.state.time}
|