datePickerField.tsx 3.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113
  1. import isPropValid from '@emotion/is-prop-valid';
  2. import {useTheme} from '@emotion/react';
  3. import styled from '@emotion/styled';
  4. import {FocusScope} from '@react-aria/focus';
  5. import moment from 'moment';
  6. import {DatePicker} from 'sentry/components/calendar';
  7. import FormField from 'sentry/components/forms/formField';
  8. import Input from 'sentry/components/input';
  9. import {Overlay, PositionWrapper} from 'sentry/components/overlay';
  10. import {IconCalendar} from 'sentry/icons';
  11. import useOverlay from 'sentry/utils/useOverlay';
  12. // XXX(epurkhiser): This is wrong, it should not be inheriting these props
  13. import {InputFieldProps, OnEvent} from './inputField';
  14. interface DatePickerFieldProps extends Omit<InputFieldProps, 'field'> {}
  15. function handleChangeDate(
  16. onChange: OnEvent,
  17. onBlur: OnEvent,
  18. date: Date,
  19. close: Function
  20. ) {
  21. onChange(date);
  22. onBlur(date);
  23. // close dropdown menu
  24. close();
  25. }
  26. export default function DatePickerField(props: DatePickerFieldProps) {
  27. const {
  28. isOpen,
  29. state: overlayState,
  30. triggerProps,
  31. overlayProps,
  32. } = useOverlay({position: 'bottom-start'});
  33. const theme = useTheme();
  34. return (
  35. <FormField {...props}>
  36. {({children: _children, onChange, onBlur, value, id, size, ...inputProps}) => {
  37. const dateObj = new Date(value);
  38. const inputValue = !isNaN(dateObj.getTime()) ? dateObj : new Date();
  39. const dateString = moment(inputValue).format('LL');
  40. return (
  41. <div>
  42. <InputWrapper id={id}>
  43. <StyledInput
  44. {...inputProps}
  45. {...triggerProps}
  46. aria-haspopup="dialog"
  47. size={size}
  48. value={dateString}
  49. readOnly
  50. />
  51. <StyledIconCalendar inputSize={size} size={size === 'xs' ? 'xs' : 'sm'} />
  52. </InputWrapper>
  53. {isOpen && (
  54. <FocusScope contain restoreFocus autoFocus>
  55. <PositionWrapper zIndex={theme.zIndex.dropdown} {...overlayProps}>
  56. <StyledOverlay>
  57. <DatePicker
  58. date={inputValue}
  59. onChange={date =>
  60. handleChangeDate(onChange, onBlur, date, overlayState.close)
  61. }
  62. />
  63. </StyledOverlay>
  64. </PositionWrapper>
  65. </FocusScope>
  66. )}
  67. </div>
  68. );
  69. }}
  70. </FormField>
  71. );
  72. }
  73. const InputWrapper = styled('div')`
  74. position: relative;
  75. `;
  76. const StyledInput = styled(Input)`
  77. text-align: left;
  78. padding-right: ${p => `calc(
  79. ${p.theme.formPadding[p.size ?? 'md'].paddingRight}px * 1.5 +
  80. ${p.theme.iconSizes.sm}
  81. )`};
  82. &:focus:not(.focus-visible) {
  83. border-color: ${p => p.theme.border};
  84. box-shadow: inset ${p => p.theme.dropShadowLight};
  85. }
  86. `;
  87. const StyledOverlay = styled(Overlay)`
  88. .rdrMonthAndYearWrapper {
  89. height: 50px;
  90. padding-top: 0;
  91. }
  92. `;
  93. const StyledIconCalendar = styled(IconCalendar, {
  94. shouldForwardProp: prop => typeof prop === 'string' && isPropValid(prop),
  95. })<{inputSize?: InputFieldProps['size']}>`
  96. position: absolute;
  97. top: 50%;
  98. right: ${p => p.theme.formPadding[p.inputSize ?? 'md'].paddingRight}px;
  99. transform: translateY(-50%);
  100. `;