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