superdesk-ui-framework 4.0.48 → 4.0.50
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/app/styles/_time.scss +28 -0
- package/app/styles/app.scss +1 -0
- package/app/styles/design-tokens/_design-tokens-general.scss +6 -3
- package/app/styles/form-elements/_checkbox.scss +5 -5
- package/app-typescript/components/DateTimePicker.tsx +9 -3
- package/app-typescript/components/ShowPopup.tsx +2 -0
- package/app-typescript/components/TimePicker.tsx +58 -4
- package/app-typescript/components/TimePickerPopover.tsx +274 -0
- package/app-typescript/utils/time.tsx +31 -0
- package/dist/components/DateTimePicker.tsx +3 -1
- package/dist/components/TimePicker.tsx +2 -2
- package/dist/examples.bundle.js +2509 -2209
- package/dist/superdesk-ui.bundle.css +29 -5
- package/dist/superdesk-ui.bundle.js +2177 -1877
- package/dist/vendor.bundle.js +18 -18
- package/examples/pages/components/DateTimePicker.tsx +3 -1
- package/examples/pages/components/TimePicker.tsx +2 -2
- package/package.json +1 -1
- package/react/components/DateTimePicker.d.ts +5 -1
- package/react/components/DateTimePicker.js +3 -3
- package/react/components/ShowPopup.d.ts +1 -0
- package/react/components/ShowPopup.js +1 -1
- package/react/components/TimePicker.d.ts +9 -2
- package/react/components/TimePicker.js +29 -4
- package/react/components/TimePickerPopover.d.ts +19 -0
- package/react/components/TimePickerPopover.js +225 -0
- package/react/utils/time.d.ts +5 -0
- package/react/utils/time.js +36 -0
@@ -0,0 +1,28 @@
|
|
1
|
+
.time-unit-highlight {
|
2
|
+
background-color: var(--sd-colour-interactive);
|
3
|
+
color: $white;
|
4
|
+
}
|
5
|
+
|
6
|
+
.time-unit {
|
7
|
+
transition: all ease 0.2s;
|
8
|
+
|
9
|
+
&:hover {
|
10
|
+
background-color: var(--sd-colour-interactive--alpha-10);
|
11
|
+
box-shadow: inset 0 0 0 1px var(--sd-colour-interactive);
|
12
|
+
cursor: pointer;
|
13
|
+
}
|
14
|
+
|
15
|
+
&:active {
|
16
|
+
background-color: var(--sd-colour-interactive--alpha-20);
|
17
|
+
}
|
18
|
+
|
19
|
+
display: flex;
|
20
|
+
justify-content: center;
|
21
|
+
width: 32px;
|
22
|
+
height: 32px;
|
23
|
+
border-radius: var(--b-radius--x-small);
|
24
|
+
font-weight: normal;
|
25
|
+
margin-inline-start: var(--gap--x-small);
|
26
|
+
margin-inline-end: var(--gap--x-small);
|
27
|
+
transition: var(--transition__menu-item);
|
28
|
+
}
|
package/app/styles/app.scss
CHANGED
@@ -31,7 +31,7 @@
|
|
31
31
|
--gap-4: calc(4 * var(--base-increment)); // 32px;
|
32
32
|
--gap--xx-large: calc(5 * var(--base-increment)); // 40px;
|
33
33
|
--gap-5: calc(5 * var(--base-increment)); // 40px;
|
34
|
-
|
34
|
+
|
35
35
|
--gap--auto: auto;
|
36
36
|
|
37
37
|
// BORDER RADIUS
|
@@ -85,10 +85,13 @@
|
|
85
85
|
--sd-drop-shadow--none: drop-shadow(0 0 0 transparent);
|
86
86
|
|
87
87
|
// FORM ELEMENTS
|
88
|
-
// Size
|
88
|
+
// Size
|
89
89
|
--form-element-height-small: var(--space--3);
|
90
90
|
--form-element-height-medium: var(--space--4);
|
91
91
|
--form-element-height-large: var(--space--5);
|
92
|
+
|
93
|
+
// TRANSITIONS
|
94
|
+
--transition__menu-item: background-color ease 0.1s
|
92
95
|
}
|
93
96
|
|
94
97
|
|
@@ -123,7 +126,7 @@
|
|
123
126
|
--sd-drop-shadow--z2: drop-shadow(0 0 0 var(--sd-shadow-outline)) drop-shadow(0 1px 4px hsla(0, 0%, 0%, 0.38)) drop-shadow(0 2px 6px hsla(0, 0%, 0%, 0.28)) drop-shadow(0 0 1px hsla(0, 0%, 0%, 0.2));
|
124
127
|
--sd-drop-shadow--z3: drop-shadow(0 0 0 var(--sd-shadow-outline)) drop-shadow(0 1px 6px hsla(0, 0%, 0%, 0.38)) drop-shadow(0 3px 8px hsla(0, 0%, 0%, 0.50)) drop-shadow(0 0 1px hsla(0, 0%, 0%, 0.2));
|
125
128
|
--sd-drop-shadow--z4: drop-shadow(0 0 0 var(--sd-shadow-outline)) drop-shadow(0 2px 10px hsla(0, 0%, 0%, 0.44)) drop-shadow(0 6px 16px hsla(0, 0%, 0%, 0.64)) drop-shadow(0 0 1px hsla(0, 0%, 0%, 0.2));
|
126
|
-
|
129
|
+
|
127
130
|
}
|
128
131
|
|
129
132
|
.sd-top-menu,
|
@@ -61,7 +61,7 @@ $checkButtonBorderRadius: $border-radius__base--small;
|
|
61
61
|
background-color: var(--sd-colour-interactive--hover);
|
62
62
|
}
|
63
63
|
}
|
64
|
-
|
64
|
+
|
65
65
|
}
|
66
66
|
|
67
67
|
// Modifier for .sd-checkbox to create a radio button
|
@@ -295,7 +295,7 @@ $checkButtonBorderRadius: $border-radius__base--small;
|
|
295
295
|
.sd-check-new--disabled, .sd-check-new[disabled="disabled"] {
|
296
296
|
opacity: 0.40;
|
297
297
|
cursor: not-allowed !important;
|
298
|
-
|
298
|
+
|
299
299
|
&:hover {
|
300
300
|
color: $checkButtonTextColor;
|
301
301
|
border-color: $checkButtonBorderColor;
|
@@ -675,8 +675,8 @@ $checkButtonBorderRadius: $border-radius__base--small;
|
|
675
675
|
& ~ label {
|
676
676
|
opacity: 1;
|
677
677
|
color: $white;
|
678
|
-
background-color: var(--sd-colour-interactive
|
679
|
-
border-color: var(--sd-colour-interactive
|
678
|
+
background-color: var(--sd-colour-interactive);
|
679
|
+
border-color: var(--sd-colour-interactive);
|
680
680
|
border-top-color: var(--sd-colour-interactive--darken-20);
|
681
681
|
box-shadow: inset 0 2px 0 0 rgba(0, 0, 0, 0.2);
|
682
682
|
}
|
@@ -748,7 +748,7 @@ $checkButtonBorderRadius: $border-radius__base--small;
|
|
748
748
|
border-top-color: var(--sd-colour-interactive--darken-20);
|
749
749
|
box-shadow: inset 0 2px 0 0 hsla(0, 0%, 0%, 0.2);
|
750
750
|
color: $white;
|
751
|
-
|
751
|
+
|
752
752
|
&:hover {
|
753
753
|
color: $white;
|
754
754
|
border-color: var(--sd-colour-interactive--active);
|
@@ -22,6 +22,8 @@ interface IPropsValueDate extends IInputWrapper {
|
|
22
22
|
disabled?: boolean;
|
23
23
|
ref?: React.LegacyRef<InputWrapper>;
|
24
24
|
'data-test-id'?: string;
|
25
|
+
timeHeaderTemplate?: React.ReactNode;
|
26
|
+
timeFooterTemplate?: React.ReactNode;
|
25
27
|
}
|
26
28
|
|
27
29
|
type IValue = {date?: string; time?: string};
|
@@ -38,6 +40,8 @@ interface IPropsValueObject extends IInputWrapper {
|
|
38
40
|
disabled?: boolean;
|
39
41
|
ref?: React.LegacyRef<InputWrapper>;
|
40
42
|
'data-test-id'?: string;
|
43
|
+
timeHeaderTemplate?: React.ReactNode;
|
44
|
+
timeFooterTemplate?: React.ReactNode;
|
41
45
|
}
|
42
46
|
|
43
47
|
type IProps = IPropsValueDate | IPropsValueObject;
|
@@ -90,13 +94,13 @@ export class DateTimePicker extends React.PureComponent<IProps> {
|
|
90
94
|
return unitOfTime.toString().padStart(2, '0');
|
91
95
|
}
|
92
96
|
|
93
|
-
getTimeValue(): string {
|
97
|
+
getTimeValue(): string | null {
|
94
98
|
if (this.props.valueType === 'date') {
|
95
99
|
return this.props.value != null
|
96
100
|
? `${this.prepareFormat(this.props.value.getHours())}:${this.prepareFormat(this.props.value.getMinutes())}`
|
97
|
-
:
|
101
|
+
: null;
|
98
102
|
} else if (this.props.valueType === 'object') {
|
99
|
-
return this.props.value.time ??
|
103
|
+
return this.props.value.time ?? null;
|
100
104
|
} else {
|
101
105
|
assertNever(this.props);
|
102
106
|
}
|
@@ -169,6 +173,8 @@ export class DateTimePicker extends React.PureComponent<IProps> {
|
|
169
173
|
allowSeconds={this.props.allowSeconds}
|
170
174
|
fullWidth={this.props.fullWidth}
|
171
175
|
required={this.props.required}
|
176
|
+
headerTemplate={this.props.timeHeaderTemplate}
|
177
|
+
footerTemplate={this.props.timeFooterTemplate}
|
172
178
|
/>
|
173
179
|
</div>
|
174
180
|
{this.props.preview !== true && (
|
@@ -10,6 +10,7 @@ interface IPropsPopupPositioner {
|
|
10
10
|
placement: Placement;
|
11
11
|
onClose(): void;
|
12
12
|
closeOnHoverEnd?: boolean;
|
13
|
+
'data-test-id'?: string;
|
13
14
|
}
|
14
15
|
|
15
16
|
export class PopupPositioner extends React.PureComponent<IPropsPopupPositioner> {
|
@@ -124,6 +125,7 @@ export class PopupPositioner extends React.PureComponent<IPropsPopupPositioner>
|
|
124
125
|
display: 'flex',
|
125
126
|
zIndex: this.zIndex,
|
126
127
|
}}
|
128
|
+
data-test-id={this.props['data-test-id']}
|
127
129
|
>
|
128
130
|
{this.props.children}
|
129
131
|
</div>,
|
@@ -2,16 +2,34 @@ import * as React from 'react';
|
|
2
2
|
import nextId from 'react-id-generator';
|
3
3
|
import {InputWrapper} from './Form';
|
4
4
|
import {IInputWrapper} from './Form/InputWrapper';
|
5
|
+
import {TimePickerPopover} from './TimePickerPopover';
|
6
|
+
import {PopupPositioner} from './ShowPopup';
|
5
7
|
|
6
8
|
interface IProps extends IInputWrapper {
|
7
|
-
value: string; //
|
9
|
+
value: string | null; // ISO8601 time string(e.g. 16:55) or null if there's no value
|
8
10
|
onChange(valueNext: string): void;
|
9
11
|
allowSeconds?: boolean;
|
12
|
+
headerTemplate?: React.ReactNode;
|
13
|
+
footerTemplate?: React.ReactNode;
|
10
14
|
'data-test-id'?: string;
|
11
15
|
}
|
12
16
|
|
13
|
-
|
17
|
+
interface IState {
|
18
|
+
popupOpen: boolean;
|
19
|
+
}
|
20
|
+
|
21
|
+
export class TimePicker extends React.PureComponent<IProps, IState> {
|
14
22
|
private htmlId = nextId();
|
23
|
+
private timeInputRef: React.RefObject<HTMLInputElement>;
|
24
|
+
|
25
|
+
constructor(props: IProps) {
|
26
|
+
super(props);
|
27
|
+
|
28
|
+
this.timeInputRef = React.createRef();
|
29
|
+
this.state = {
|
30
|
+
popupOpen: false,
|
31
|
+
};
|
32
|
+
}
|
15
33
|
|
16
34
|
render() {
|
17
35
|
if (this.props.preview) {
|
@@ -34,11 +52,47 @@ export class TimePicker extends React.PureComponent<IProps> {
|
|
34
52
|
labelHidden={this.props.labelHidden}
|
35
53
|
htmlId={this.htmlId}
|
36
54
|
tabindex={this.props.tabindex}
|
37
|
-
inputWrapper={this.props.inputWrapper}
|
38
55
|
>
|
56
|
+
{this.state.popupOpen && (
|
57
|
+
<PopupPositioner
|
58
|
+
getReferenceElement={() => this.timeInputRef.current as HTMLElement}
|
59
|
+
placement="bottom-start"
|
60
|
+
onClose={() => {
|
61
|
+
this.setState({
|
62
|
+
popupOpen: false,
|
63
|
+
});
|
64
|
+
}}
|
65
|
+
data-test-id="time-picker-popover"
|
66
|
+
>
|
67
|
+
<TimePickerPopover
|
68
|
+
value={this.props.value}
|
69
|
+
onChange={this.props.onChange}
|
70
|
+
closePopup={() => {
|
71
|
+
this.setState({
|
72
|
+
popupOpen: false,
|
73
|
+
});
|
74
|
+
}}
|
75
|
+
allowSeconds={this.props.allowSeconds}
|
76
|
+
headerTemplate={this.props.headerTemplate}
|
77
|
+
footerTemplate={this.props.footerTemplate}
|
78
|
+
/>
|
79
|
+
</PopupPositioner>
|
80
|
+
)}
|
39
81
|
<input
|
40
|
-
|
82
|
+
style={{
|
83
|
+
cursor: 'pointer',
|
84
|
+
}}
|
85
|
+
ref={this.timeInputRef}
|
86
|
+
value={this.props.value ?? ''}
|
41
87
|
type="time"
|
88
|
+
onClick={(e) => {
|
89
|
+
// don't show default popup
|
90
|
+
e.preventDefault();
|
91
|
+
|
92
|
+
this.setState({
|
93
|
+
popupOpen: true,
|
94
|
+
});
|
95
|
+
}}
|
42
96
|
className="sd-input__input"
|
43
97
|
id={this.htmlId}
|
44
98
|
aria-labelledby={this.htmlId + 'label'}
|
@@ -0,0 +1,274 @@
|
|
1
|
+
import * as React from 'react';
|
2
|
+
import {classnames, Spacer} from '@sourcefabric/common';
|
3
|
+
import {ContentDivider} from './ContentDivider';
|
4
|
+
import {RadioButtonGroup} from './RadioButtonGroup';
|
5
|
+
import {getOptionsForTimeUnit, ITimeUnit, padValue} from '../utils/time';
|
6
|
+
import {assertNever} from '../helpers';
|
7
|
+
|
8
|
+
interface IProps {
|
9
|
+
closePopup: () => void;
|
10
|
+
headerTemplate?: React.ReactNode;
|
11
|
+
footerTemplate?: React.ReactNode;
|
12
|
+
allowSeconds?: boolean;
|
13
|
+
onChange: (nextValue: string) => void;
|
14
|
+
value: string | null;
|
15
|
+
}
|
16
|
+
|
17
|
+
interface IPropsTimeValueHolder {
|
18
|
+
isActive?: boolean;
|
19
|
+
value: string;
|
20
|
+
onClick(event: React.MouseEvent<HTMLSpanElement>): void;
|
21
|
+
}
|
22
|
+
|
23
|
+
class TimeValueHolder extends React.PureComponent<IPropsTimeValueHolder> {
|
24
|
+
spanEl: React.RefObject<HTMLSpanElement>;
|
25
|
+
|
26
|
+
constructor(props: IPropsTimeValueHolder) {
|
27
|
+
super(props);
|
28
|
+
|
29
|
+
this.spanEl = React.createRef();
|
30
|
+
}
|
31
|
+
|
32
|
+
public scrollToValue() {
|
33
|
+
this.spanEl.current?.scrollIntoView();
|
34
|
+
}
|
35
|
+
|
36
|
+
render() {
|
37
|
+
return (
|
38
|
+
<span
|
39
|
+
ref={this.props.isActive ? this.spanEl : undefined}
|
40
|
+
onClick={this.props.onClick}
|
41
|
+
className={classnames('p-1 time-unit', {
|
42
|
+
'time-unit-highlight': this.props.isActive ?? false,
|
43
|
+
})}
|
44
|
+
>
|
45
|
+
{this.props.value}
|
46
|
+
</span>
|
47
|
+
);
|
48
|
+
}
|
49
|
+
}
|
50
|
+
|
51
|
+
function parseUnitOfTime(unit: ITimeUnit, value: string | null, is12HourFormat?: boolean): string {
|
52
|
+
const [hour, minutes, seconds] = (value ?? '').split(':');
|
53
|
+
const valueForUnit = (() => {
|
54
|
+
if (unit === 'hours') {
|
55
|
+
/**
|
56
|
+
* Hour value is always in 24-hour format, so we need to adjust it
|
57
|
+
* to 12-hour if needed.
|
58
|
+
*/
|
59
|
+
if (is12HourFormat) {
|
60
|
+
return hour === '00' ? '12' : hour;
|
61
|
+
} else {
|
62
|
+
return hour;
|
63
|
+
}
|
64
|
+
} else if (unit === 'minutes') {
|
65
|
+
return minutes;
|
66
|
+
} else if (unit === 'seconds') {
|
67
|
+
return seconds;
|
68
|
+
} else {
|
69
|
+
assertNever(unit);
|
70
|
+
}
|
71
|
+
})();
|
72
|
+
|
73
|
+
const valueParsed =
|
74
|
+
is12HourFormat && unit === 'hours' && valueForUnit !== '12'
|
75
|
+
? parseInt(valueForUnit, 10) % 12
|
76
|
+
: parseInt(valueForUnit, 10);
|
77
|
+
|
78
|
+
return padValue(valueParsed);
|
79
|
+
}
|
80
|
+
|
81
|
+
export class TimePickerPopover extends React.PureComponent<IProps> {
|
82
|
+
private is12HourFormat: boolean;
|
83
|
+
|
84
|
+
// hour, minutes, seconds
|
85
|
+
private inputRefs: Array<React.RefObject<TimeValueHolder>>;
|
86
|
+
|
87
|
+
constructor(props: IProps) {
|
88
|
+
super(props);
|
89
|
+
|
90
|
+
this.inputRefs = [React.createRef(), React.createRef(), React.createRef()];
|
91
|
+
this.handleChange = this.handleChange.bind(this);
|
92
|
+
|
93
|
+
const hour = new Date().toLocaleTimeString([]);
|
94
|
+
this.is12HourFormat = hour.includes('AM') || hour.includes('PM');
|
95
|
+
}
|
96
|
+
|
97
|
+
handleChange(unit: ITimeUnit, value: string) {
|
98
|
+
const fallbackDate = new Date();
|
99
|
+
const [hour, minutes, seconds] =
|
100
|
+
this.props.value == null
|
101
|
+
? [
|
102
|
+
padValue(fallbackDate.getHours()),
|
103
|
+
padValue(fallbackDate.getMinutes()),
|
104
|
+
padValue(fallbackDate.getSeconds()),
|
105
|
+
]
|
106
|
+
: this.props.value.split(':');
|
107
|
+
let nextValue = '';
|
108
|
+
|
109
|
+
if (unit === 'hours') {
|
110
|
+
nextValue = `${value}:${minutes}`;
|
111
|
+
} else if (unit === 'minutes') {
|
112
|
+
nextValue = `${hour}:${value}`;
|
113
|
+
} else if (unit === 'seconds') {
|
114
|
+
nextValue = `${hour}:${minutes}:${value}`;
|
115
|
+
} else {
|
116
|
+
assertNever(unit);
|
117
|
+
}
|
118
|
+
|
119
|
+
if (this.props.allowSeconds && unit !== 'seconds') {
|
120
|
+
nextValue += `:${seconds}`;
|
121
|
+
}
|
122
|
+
|
123
|
+
this.props.onChange(nextValue);
|
124
|
+
}
|
125
|
+
|
126
|
+
componentDidMount(): void {
|
127
|
+
this.inputRefs.forEach((unitOfTime) => unitOfTime?.current?.scrollToValue?.());
|
128
|
+
}
|
129
|
+
|
130
|
+
render(): React.ReactNode {
|
131
|
+
const styleForColumnOfUnit: React.CSSProperties = {
|
132
|
+
maxHeight: 190,
|
133
|
+
overflowY: 'auto',
|
134
|
+
scrollbarWidth: 'none',
|
135
|
+
marginTop: 'var(--gap-1)',
|
136
|
+
};
|
137
|
+
|
138
|
+
return (
|
139
|
+
<div className="sd-shadow--z2 radius-md" onBlur={this.props.closePopup}>
|
140
|
+
<Spacer
|
141
|
+
v
|
142
|
+
gap="0"
|
143
|
+
style={{
|
144
|
+
width: 200,
|
145
|
+
padding: 'var(--gap-1)',
|
146
|
+
backgroundColor: 'var(--color-bg-00)',
|
147
|
+
borderRadius: 'var(--b-radius--small)',
|
148
|
+
}}
|
149
|
+
>
|
150
|
+
{this.props.headerTemplate && (
|
151
|
+
<>
|
152
|
+
{this.props.headerTemplate}
|
153
|
+
<ContentDivider border type="solid" orientation="horizontal" margin="none" />
|
154
|
+
</>
|
155
|
+
)}
|
156
|
+
<Spacer h gap="4" noWrap justifyContent="center" alignItems="start">
|
157
|
+
<Spacer v gap="4" style={styleForColumnOfUnit} alignItems="center" noWrap>
|
158
|
+
{getOptionsForTimeUnit('hours', this.is12HourFormat).map((hour) => {
|
159
|
+
const isActiveHour =
|
160
|
+
hour === parseUnitOfTime('hours', this.props.value, this.is12HourFormat);
|
161
|
+
|
162
|
+
return (
|
163
|
+
<TimeValueHolder
|
164
|
+
ref={isActiveHour ? this.inputRefs[0] : undefined}
|
165
|
+
onClick={() => {
|
166
|
+
this.handleChange('hours', hour);
|
167
|
+
}}
|
168
|
+
isActive={isActiveHour}
|
169
|
+
value={hour}
|
170
|
+
/>
|
171
|
+
);
|
172
|
+
})}
|
173
|
+
</Spacer>
|
174
|
+
<ContentDivider align="center" border type="solid" orientation="vertical" margin="none" />
|
175
|
+
<Spacer v gap="4" style={styleForColumnOfUnit} alignItems="center" noWrap>
|
176
|
+
{getOptionsForTimeUnit('minutes', this.is12HourFormat).map((minute) => {
|
177
|
+
const isActiveMinute =
|
178
|
+
minute === parseUnitOfTime('minutes', this.props.value, this.is12HourFormat);
|
179
|
+
|
180
|
+
return (
|
181
|
+
<TimeValueHolder
|
182
|
+
ref={isActiveMinute ? this.inputRefs[1] : undefined}
|
183
|
+
isActive={isActiveMinute}
|
184
|
+
value={minute}
|
185
|
+
onClick={() => {
|
186
|
+
this.handleChange('minutes', minute);
|
187
|
+
}}
|
188
|
+
/>
|
189
|
+
);
|
190
|
+
})}
|
191
|
+
</Spacer>
|
192
|
+
{this.props.allowSeconds && (
|
193
|
+
<>
|
194
|
+
<ContentDivider
|
195
|
+
align="center"
|
196
|
+
border
|
197
|
+
type="solid"
|
198
|
+
orientation="vertical"
|
199
|
+
margin="none"
|
200
|
+
/>
|
201
|
+
<Spacer v gap="4" style={styleForColumnOfUnit} alignItems="center" noWrap>
|
202
|
+
{getOptionsForTimeUnit('seconds', this.is12HourFormat).map((second) => {
|
203
|
+
const isActiveMinute =
|
204
|
+
second ===
|
205
|
+
parseUnitOfTime('seconds', this.props.value, this.is12HourFormat);
|
206
|
+
|
207
|
+
return (
|
208
|
+
<TimeValueHolder
|
209
|
+
ref={isActiveMinute ? this.inputRefs[2] : undefined}
|
210
|
+
onClick={() => {
|
211
|
+
this.handleChange('seconds', second);
|
212
|
+
}}
|
213
|
+
isActive={isActiveMinute}
|
214
|
+
value={second}
|
215
|
+
/>
|
216
|
+
);
|
217
|
+
})}
|
218
|
+
</Spacer>
|
219
|
+
</>
|
220
|
+
)}
|
221
|
+
{this.is12HourFormat && (
|
222
|
+
<div
|
223
|
+
style={{
|
224
|
+
marginTop: 'var(--gap-1)',
|
225
|
+
}}
|
226
|
+
>
|
227
|
+
<RadioButtonGroup
|
228
|
+
onChange={(nextValue) => {
|
229
|
+
const [hour, minutes, seconds] = (this.props.value ?? '').split(':');
|
230
|
+
|
231
|
+
if (nextValue === 'PM') {
|
232
|
+
let newValue = `${padValue(parseInt(hour, 10) + 12)}:${minutes}`;
|
233
|
+
|
234
|
+
if (this.props.allowSeconds) {
|
235
|
+
newValue += `:${seconds}`;
|
236
|
+
}
|
237
|
+
|
238
|
+
this.props.onChange(newValue);
|
239
|
+
} else {
|
240
|
+
let newValue = `${padValue(parseInt(hour, 10) - 12)}:${minutes}`;
|
241
|
+
|
242
|
+
if (this.props.allowSeconds) {
|
243
|
+
newValue += `:${seconds}`;
|
244
|
+
}
|
245
|
+
|
246
|
+
this.props.onChange(newValue);
|
247
|
+
}
|
248
|
+
}}
|
249
|
+
options={[
|
250
|
+
{
|
251
|
+
label: 'AM',
|
252
|
+
value: 'AM',
|
253
|
+
},
|
254
|
+
{
|
255
|
+
label: 'PM',
|
256
|
+
value: 'PM',
|
257
|
+
},
|
258
|
+
]}
|
259
|
+
value={parseInt((this.props.value ?? '').split(':')[0], 10) < 12 ? 'AM' : 'PM'}
|
260
|
+
/>
|
261
|
+
</div>
|
262
|
+
)}
|
263
|
+
</Spacer>
|
264
|
+
{this.props.footerTemplate && (
|
265
|
+
<>
|
266
|
+
<ContentDivider border type="solid" orientation="horizontal" margin="none" />
|
267
|
+
{this.props.footerTemplate}
|
268
|
+
</>
|
269
|
+
)}
|
270
|
+
</Spacer>
|
271
|
+
</div>
|
272
|
+
);
|
273
|
+
}
|
274
|
+
}
|
@@ -0,0 +1,31 @@
|
|
1
|
+
import {range} from 'lodash';
|
2
|
+
|
3
|
+
export type ITimeUnit = 'hours' | 'minutes' | 'seconds';
|
4
|
+
|
5
|
+
export function getOptionsForTimeUnit(
|
6
|
+
timeUnit: ITimeUnit,
|
7
|
+
is12HourFormat?: boolean,
|
8
|
+
disabledOptions?: {[key: string]: Array<number>},
|
9
|
+
): Array<string> {
|
10
|
+
const format12HourArr = [12, ...range(1, 12)];
|
11
|
+
|
12
|
+
const timeUnitArray = (() => {
|
13
|
+
if (timeUnit === 'hours') {
|
14
|
+
if (is12HourFormat) {
|
15
|
+
return format12HourArr;
|
16
|
+
} else {
|
17
|
+
return range(24);
|
18
|
+
}
|
19
|
+
} else {
|
20
|
+
return range(60);
|
21
|
+
}
|
22
|
+
})();
|
23
|
+
|
24
|
+
return timeUnitArray
|
25
|
+
.filter((item) => !(disabledOptions?.[timeUnit] ?? []).includes(item))
|
26
|
+
.map((value) => value.toString().padStart(2, '0'));
|
27
|
+
}
|
28
|
+
|
29
|
+
export function padValue(value: number) {
|
30
|
+
return value.toString().padStart(2, '0');
|
31
|
+
}
|
@@ -7,7 +7,7 @@ class DateTimePickerExample extends React.PureComponent<{}, {dateTime: Date | nu
|
|
7
7
|
super(props);
|
8
8
|
|
9
9
|
this.state = {
|
10
|
-
dateTime:
|
10
|
+
dateTime: null,
|
11
11
|
};
|
12
12
|
}
|
13
13
|
|
@@ -17,6 +17,8 @@ class DateTimePickerExample extends React.PureComponent<{}, {dateTime: Date | nu
|
|
17
17
|
label="Planning date"
|
18
18
|
labelHidden
|
19
19
|
inlineLabel
|
20
|
+
fullWidth
|
21
|
+
valueType="date"
|
20
22
|
value={this.state.dateTime}
|
21
23
|
dateFormat="YYYY-MM-DD"
|
22
24
|
fullWidth
|
@@ -7,12 +7,12 @@ import {TimePickerV2} from '../../../app-typescript/components/TimePickerV2';
|
|
7
7
|
let minutes = Array.from(Array(60).keys());
|
8
8
|
let changedMinutes = minutes.filter((num) => num % 15 !== 0);
|
9
9
|
|
10
|
-
class TimePickerExample extends React.PureComponent<{}, {time: string}> {
|
10
|
+
class TimePickerExample extends React.PureComponent<{}, {time: string | null}> {
|
11
11
|
constructor(props) {
|
12
12
|
super(props);
|
13
13
|
|
14
14
|
this.state = {
|
15
|
-
time:
|
15
|
+
time: null,
|
16
16
|
};
|
17
17
|
}
|
18
18
|
|