Skip to content

Commit bbcfa0c

Browse files
author
Windmillcode
committed
[UPDATE] additional work
1 parent b04ff8a commit bbcfa0c

2 files changed

Lines changed: 275 additions & 1 deletion

File tree

Lines changed: 267 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,267 @@
1+
// import { FormInputSize } from '../Forms/types';
2+
// import { Icon } from '../Icon/Icon';
3+
import 'rc-picker/assets/index.css';
4+
5+
import { css, cx } from '@emotion/css';
6+
import { DateTime, dateTime, dateTimeAsMoment, GrafanaTheme2, isDateTimeInput } from '@grafana/data';
7+
import { FormInputSize, Icon, useStyles2 } from '@grafana/ui';
8+
import { DateTime as LuxonDateTime } from 'luxon';
9+
import RcPicker, { PickerProps } from 'rc-picker';
10+
import generateConfig from 'rc-picker/lib/generate/luxon'; // Changed from moment to luxon
11+
import locale from 'rc-picker/lib/locale/en_US';
12+
import * as React from 'react';
13+
14+
// import { useStyles2 } from '../../themes/ThemeContext';
15+
import { getFocusStyles } from '../../themes/mixins';
16+
import { inputSizes } from '../Forms/commonStyles';
17+
18+
interface BaseProps {
19+
onChange: (value: DateTime) => void | ((value?: DateTime) => void);
20+
value?: DateTime;
21+
showHour?: boolean;
22+
showSeconds?: boolean;
23+
minuteStep?: PickerProps['minuteStep'];
24+
size?: FormInputSize;
25+
disabled?: boolean;
26+
disabledHours?: () => number[];
27+
disabledMinutes?: () => number[];
28+
disabledSeconds?: () => number[];
29+
placeholder?: string;
30+
allowEmpty?: boolean;
31+
}
32+
33+
interface AllowEmptyProps extends BaseProps {
34+
allowEmpty: true;
35+
onChange: (value?: DateTime) => void;
36+
}
37+
38+
interface NoAllowEmptyProps extends BaseProps {
39+
allowEmpty?: false;
40+
onChange: (value: DateTime) => void;
41+
}
42+
43+
export type Props = AllowEmptyProps | NoAllowEmptyProps;
44+
45+
export const POPUP_CLASS_NAME = 'time-of-day-picker-panel';
46+
47+
export const TimeOfDayPicker = ({
48+
minuteStep = 1,
49+
showHour = true,
50+
showSeconds = false,
51+
value,
52+
size = 'auto',
53+
disabled,
54+
disabledHours,
55+
disabledMinutes,
56+
disabledSeconds,
57+
placeholder,
58+
// @ts-ignore
59+
use12Hours,
60+
// note: we can't destructure allowEmpty/onChange here
61+
// in order to discriminate the types properly later in the onChange handler
62+
...restProps
63+
}: Props & { use12Hours?: boolean }) => {
64+
const styles = useStyles2(getStyles);
65+
const allowClear = restProps.allowEmpty ?? false;
66+
67+
return (
68+
<RcPicker<LuxonDateTime> // Changed from Moment to LuxonDateTime
69+
generateConfig={generateConfig}
70+
locale={locale}
71+
allowClear={
72+
allowClear && {
73+
clearIcon: <Icon name="times" className={styles.clearIcon} />,
74+
}
75+
}
76+
className={cx(inputSizes()[size], styles.input)}
77+
classNames={{
78+
popup: cx(styles.picker, POPUP_CLASS_NAME),
79+
}}
80+
defaultValue={restProps.allowEmpty ? undefined : toLuxonDateTime(dateTime())} // Updated to use Luxon conversion
81+
disabled={disabled}
82+
disabledTime={() => ({
83+
disabledHours,
84+
disabledMinutes,
85+
disabledSeconds,
86+
})}
87+
format={generateFormat(showHour, showSeconds, use12Hours)}
88+
minuteStep={minuteStep}
89+
onChange={(value) => {
90+
if (value && !Array.isArray(value)) {
91+
if (restProps.allowEmpty) {
92+
return restProps.onChange(value ? fromLuxonDateTime(value) : undefined);
93+
} else {
94+
return restProps.onChange(fromLuxonDateTime(value));
95+
}
96+
}
97+
}}
98+
use12Hours={use12Hours}
99+
picker="time"
100+
placeholder={placeholder}
101+
showNow={false}
102+
needConfirm={false}
103+
suffixIcon={<Caret wrapperStyle={styles.caretWrapper} />}
104+
value={value ? toLuxonDateTime(value) : undefined} // Updated to use Luxon conversion
105+
/>
106+
);
107+
};
108+
109+
// Helper functions to convert between Grafana DateTime and Luxon DateTime
110+
function toLuxonDateTime(grafanaDateTime: DateTime): LuxonDateTime {
111+
return LuxonDateTime.fromJSDate(grafanaDateTime.toDate());
112+
}
113+
114+
function fromLuxonDateTime(luxonDateTime: LuxonDateTime): DateTime {
115+
return dateTime(luxonDateTime.toJSDate());
116+
}
117+
118+
function generateFormat(
119+
showHour = true,
120+
showSeconds = false,
121+
use12Hours = false // Optional parameter for 12-hour format
122+
) {
123+
const maybeHour = showHour
124+
? use12Hours
125+
? 'h:'
126+
: 'H:' // 'h' for 12-hour, 'H' for 24-hour (no leading zero)
127+
: '';
128+
129+
const maybeSecond = showSeconds ? ':ss' : '';
130+
const amPm = use12Hours ? ' a' : ''; // Changed to lowercase 'a' for Luxon format
131+
132+
return maybeHour + 'mm' + maybeSecond + amPm;
133+
}
134+
135+
interface CaretProps {
136+
wrapperStyle?: string;
137+
}
138+
139+
const Caret = ({ wrapperStyle = '' }: CaretProps) => {
140+
return (
141+
<div className={wrapperStyle}>
142+
<Icon name="angle-down" />
143+
</div>
144+
);
145+
};
146+
147+
const getStyles = (theme: GrafanaTheme2) => {
148+
const bgColor = theme.components.input.background;
149+
const optionBgHover = theme.colors.action.hover;
150+
const borderRadius = theme.shape.radius.default;
151+
const borderColor = theme.components.input.borderColor;
152+
153+
return {
154+
caretWrapper: css({
155+
position: 'relative',
156+
top: '50%',
157+
transform: 'translateY(-50%)',
158+
display: 'inline-block',
159+
color: theme.colors.text.secondary,
160+
}),
161+
clearIcon: css({
162+
color: theme.colors.text.secondary,
163+
164+
'&:hover': {
165+
color: theme.colors.text.maxContrast,
166+
},
167+
}),
168+
picker: css({
169+
'&.rc-picker-dropdown': {
170+
boxShadow: 'none',
171+
zIndex: theme.zIndex.portal,
172+
},
173+
'.rc-picker-time-panel-column': {
174+
fontSize: theme.typography.htmlFontSize,
175+
backgroundColor: bgColor,
176+
color: theme.colors.text.secondary,
177+
padding: 'unset',
178+
overflowY: 'auto',
179+
scrollbarWidth: 'thin',
180+
width: theme.spacing(8),
181+
li: {
182+
paddingRight: theme.spacing(2),
183+
width: 'auto',
184+
'&.rc-picker-time-panel-cell-selected': {
185+
backgroundColor: 'inherit',
186+
border: `1px solid ${theme.colors.action.selectedBorder}`,
187+
borderRadius,
188+
color: theme.colors.text.primary,
189+
},
190+
191+
'&:hover': {
192+
background: optionBgHover,
193+
color: theme.colors.text.primary,
194+
},
195+
196+
'&.rc-picker-time-panel-cell-disabled': {
197+
color: theme.colors.action.disabledText,
198+
},
199+
},
200+
201+
'.rc-picker-time-panel-cell-inner': {
202+
color: 'inherit',
203+
},
204+
205+
'&:not(:last-of-type)': {
206+
borderRight: `1px solid ${borderColor}`,
207+
},
208+
},
209+
210+
'.rc-picker-panel': {
211+
boxShadow: theme.shadows.z3,
212+
backgroundColor: bgColor,
213+
borderColor,
214+
borderRadius,
215+
overflow: 'hidden',
216+
},
217+
}),
218+
input: css({
219+
'&.rc-picker-focused': {
220+
border: 'none',
221+
222+
'.rc-picker-input': getFocusStyles(theme),
223+
},
224+
225+
'&.rc-picker-disabled': {
226+
'.rc-picker-input': {
227+
backgroundColor: theme.colors.action.disabledBackground,
228+
color: theme.colors.action.disabledText,
229+
border: `1px solid ${theme.colors.action.disabledBackground}`,
230+
'&:focus': {
231+
boxShadow: 'none',
232+
},
233+
},
234+
},
235+
236+
'.rc-picker-input': {
237+
backgroundColor: bgColor,
238+
borderRadius,
239+
borderColor,
240+
borderStyle: 'solid',
241+
borderWidth: '1px',
242+
color: theme.colors.text.primary,
243+
height: theme.spacing(4),
244+
padding: theme.spacing(0, 1),
245+
246+
input: {
247+
color: 'unset',
248+
backgroundColor: 'unset',
249+
'&:focus': {
250+
outline: 'none',
251+
},
252+
253+
'&::placeholder': {
254+
color: theme.colors.text.disabled,
255+
},
256+
},
257+
},
258+
259+
'.rc-picker-clear': {
260+
alignItems: 'center',
261+
display: 'flex',
262+
insetInlineEnd: 'unset',
263+
position: 'relative',
264+
},
265+
}),
266+
};
267+
};

‎yarn.lock‎

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3381,6 +3381,13 @@ __metadata:
33813381
languageName: node
33823382
linkType: hard
33833383

3384+
"@grafana/i18n@npm:0.0.1":
3385+
version: 0.0.1
3386+
resolution: "@grafana/i18n@npm:0.0.1"
3387+
checksum: 10/79844f8627197672b84e59e565b57d9a04acb7b333688ea552218f6313a633913661b955dfa93dcd59f6141a0079cb1e9cfef777ff7b869e17539dbaba2e4ca3
3388+
languageName: node
3389+
linkType: hard
3390+
33843391
"@grafana/i18n@npm:12.1.0-pre, @grafana/i18n@workspace:*, @grafana/i18n@workspace:packages/grafana-i18n":
33853392
version: 0.0.0-use.local
33863393
resolution: "@grafana/i18n@workspace:packages/grafana-i18n"
@@ -11312,7 +11319,7 @@ __metadata:
1131211319
"@grafana/data": "npm:12.1.0-pre"
1131311320
"@grafana/e2e-selectors": "npm:12.1.0-pre"
1131411321
"@grafana/faro-web-sdk": "npm:^1.13.2"
11315-
"@grafana/i18n": "npm:12.1.0-pre"
11322+
"@grafana/i18n": "npm:0.0.1"
1131611323
"@grafana/schema": "npm:12.1.0-pre"
1131711324
"@grafana/tsconfig": "npm:^2.0.0"
1131811325
"@hello-pangea/dnd": "npm:17.0.0"

0 commit comments

Comments
 (0)