1- import React , { useState , useRef , useEffect , useId } from "react" ;
1+ import React , { useState , useRef , useEffect , useId , useCallback } from "react" ;
22import dayjs from "dayjs" ;
33import styled , { ThemeProvider } from "styled-components" ;
44import useTheme from "../useTheme" ;
55import useTranslatedLabels from "../useTranslatedLabels" ;
6- import DxcTextInput from "../text-input/TextInput" ;
76import DateInputPropsType , { RefType } from "./types" ;
8- import DxcDatePicker from "./DatePicker" ;
7+ import DatePicker from "./DatePicker" ;
98import * as Popover from "@radix-ui/react-popover" ;
109import customParseFormat from "dayjs/plugin/customParseFormat" ;
10+ import { getMargin } from "../common/utils" ;
11+ import { spaces } from "../common/variables" ;
12+ import DxcTextInput from "../text-input/TextInput" ;
1113
1214dayjs . extend ( customParseFormat ) ;
1315
16+ const SIDEOFFSET = 4 ;
17+
1418const getValueForPicker = ( value , format ) => dayjs ( value , format . toUpperCase ( ) , true ) ;
1519
1620const getDate = ( value , format , lastValidYear , setLastValidYear ) => {
@@ -25,9 +29,7 @@ const getDate = (value, format, lastValidYear, setLastValidYear) => {
2529 setLastValidYear ( 1900 + + newDate . format ( "YY" ) ) ;
2630 newDate = newDate . set ( "year" , 1900 + + newDate . format ( "YY" ) ) ;
2731 }
28- } else {
29- newDate = newDate . set ( "year" , ( lastValidYear <= 1999 ? 1900 : 2000 ) + + newDate . format ( "YY" ) ) ;
30- }
32+ } else newDate = newDate . set ( "year" , ( lastValidYear <= 1999 ? 1900 : 2000 ) + + newDate . format ( "YY" ) ) ;
3133 return newDate ;
3234 }
3335} ;
@@ -67,26 +69,11 @@ const DxcDateInput = React.forwardRef<RefType, DateInputPropsType>(
6769 : 1900
6870 : undefined
6971 ) ;
72+ const [ sideOffset , setSideOffset ] = useState ( SIDEOFFSET ) ;
7073 const colorsTheme = useTheme ( ) ;
7174 const translatedLabels = useTranslatedLabels ( ) ;
7275 const dateRef = useRef ( null ) ;
73-
74- useEffect ( ( ) => {
75- if ( value || value === "" ) setDayjsDate ( getDate ( value , format , lastValidYear , setLastValidYear ) ) ;
76- } , [ value , format , lastValidYear ] ) ;
77-
78- useEffect ( ( ) => {
79- if ( ! disabled ) {
80- const actionButtonRef = dateRef ?. current . querySelector ( "[title='Select date']" ) ;
81- actionButtonRef ?. setAttribute ( "aria-haspopup" , true ) ;
82- actionButtonRef ?. setAttribute ( "role" , "combobox" ) ;
83- actionButtonRef ?. setAttribute ( "aria-expanded" , isOpen ) ;
84- actionButtonRef ?. setAttribute ( "aria-controls" , calendarId ) ;
85- if ( isOpen ) {
86- actionButtonRef ?. setAttribute ( "aria-describedby" , calendarId ) ;
87- }
88- }
89- } , [ isOpen , disabled , calendarId ] ) ;
76+ const popoverContentRef = useRef ( null ) ;
9077
9178 const handleCalendarOnClick = ( newDate ) => {
9279 const newValue = newDate . format ( format . toUpperCase ( ) ) ;
@@ -139,8 +126,24 @@ const DxcDateInput = React.forwardRef<RefType, DateInputPropsType>(
139126 : onBlur ?.( callbackParams ) ;
140127 } ;
141128
129+ const adjustSideOffset = useCallback ( ( ) => {
130+ if ( error != null ) {
131+ setTimeout ( ( ) => {
132+ if ( popoverContentRef . current && dateRef . current ) {
133+ const popoverRect = popoverContentRef . current . getBoundingClientRect ( ) ;
134+ const triggerRect = dateRef . current . querySelector ( '[id^="input"]' ) ?. getBoundingClientRect ( ) ;
135+ const errorMessageHeight = dateRef . current
136+ . querySelector ( '[id^="error-input"]' )
137+ ?. getBoundingClientRect ( ) . height ;
138+ setSideOffset ( popoverRect . top > triggerRect . bottom ? - errorMessageHeight : SIDEOFFSET ) ;
139+ }
140+ } , 0 ) ;
141+ }
142+ } , [ error ] ) ;
143+
142144 const openCalendar = ( ) => {
143145 setIsOpen ( ! isOpen ) ;
146+ adjustSideOffset ( ) ;
144147 } ;
145148 const closeCalendar = ( ) => {
146149 setIsOpen ( false ) ;
@@ -158,17 +161,49 @@ const DxcDateInput = React.forwardRef<RefType, DateInputPropsType>(
158161 if ( ! event ?. currentTarget . contains ( event . relatedTarget ) ) closeCalendar ( ) ;
159162 } ;
160163
164+ useEffect ( ( ) => {
165+ window . addEventListener ( "scroll" , adjustSideOffset ) ;
166+ return ( ) => {
167+ window . removeEventListener ( "scroll" , adjustSideOffset ) ;
168+ } ;
169+ } , [ adjustSideOffset ] ) ;
170+
171+ useEffect ( ( ) => {
172+ if ( value || value === "" ) setDayjsDate ( getDate ( value , format , lastValidYear , setLastValidYear ) ) ;
173+ } , [ value , format , lastValidYear ] ) ;
174+
175+ useEffect ( ( ) => {
176+ if ( ! disabled ) {
177+ const actionButtonRef = dateRef ?. current . querySelector ( "[title='Select date']" ) ;
178+ actionButtonRef ?. setAttribute ( "aria-haspopup" , true ) ;
179+ actionButtonRef ?. setAttribute ( "role" , "combobox" ) ;
180+ actionButtonRef ?. setAttribute ( "aria-expanded" , isOpen ) ;
181+ actionButtonRef ?. setAttribute ( "aria-controls" , calendarId ) ;
182+ if ( isOpen ) {
183+ actionButtonRef ?. setAttribute ( "aria-describedby" , calendarId ) ;
184+ }
185+ }
186+ } , [ isOpen , disabled , calendarId ] ) ;
187+
161188 return (
162189 < ThemeProvider theme = { colorsTheme } >
163- < DateInputContainer size = { size } ref = { ref } >
190+ < DateInputContainer margin = { margin } size = { size } ref = { ref } >
191+ { label && (
192+ < Label
193+ htmlFor = { dateRef . current ?. getElementsByTagName ( "input" ) [ 0 ] . id }
194+ disabled = { disabled }
195+ hasHelperText = { helperText ? true : false }
196+ >
197+ { label } { optional && < OptionalLabel > { translatedLabels . formFields . optionalLabel } </ OptionalLabel > }
198+ </ Label >
199+ ) }
200+ { helperText && < HelperText disabled = { disabled } > { helperText } </ HelperText > }
164201 < Popover . Root open = { isOpen } >
165202 < Popover . Trigger asChild aria-controls = { undefined } >
166203 < DxcTextInput
167- label = { label }
168204 name = { name }
169205 defaultValue = { defaultValue }
170206 value = { value ?? innerValue }
171- helperText = { helperText }
172207 placeholder = { placeholder ? format . toUpperCase ( ) : null }
173208 action = { {
174209 onClick : openCalendar ,
@@ -183,22 +218,21 @@ const DxcDateInput = React.forwardRef<RefType, DateInputPropsType>(
183218 onBlur = { handleOnBlur }
184219 error = { error }
185220 autocomplete = { autocomplete }
186- margin = { margin }
187221 size = { size }
188222 tabIndex = { tabIndex }
189223 ref = { dateRef }
190224 />
191225 </ Popover . Trigger >
192226 < Popover . Portal >
193227 < StyledPopoverContent
194- sideOffset = { error ? - 18 : 2 }
228+ sideOffset = { sideOffset }
195229 align = "end"
196230 aria-modal = { true }
197231 onBlur = { handleDatePickerOnBlur }
198232 onKeyDown = { handleDatePickerEscKeydown }
199- avoidCollisions = { false }
233+ ref = { popoverContentRef }
200234 >
201- < DxcDatePicker id = { calendarId } onDateSelect = { handleCalendarOnClick } date = { dayjsDate } />
235+ < DatePicker id = { calendarId } onDateSelect = { handleCalendarOnClick } date = { dayjsDate } />
202236 </ StyledPopoverContent >
203237 </ Popover . Portal >
204238 </ Popover . Root >
@@ -208,15 +242,68 @@ const DxcDateInput = React.forwardRef<RefType, DateInputPropsType>(
208242 }
209243) ;
210244
245+ const sizes = {
246+ small : "240px" ,
247+ medium : "360px" ,
248+ large : "480px" ,
249+ fillParent : "100%" ,
250+ } ;
251+
252+ const calculateWidth = ( margin , size ) =>
253+ size === "fillParent"
254+ ? `calc(${ sizes [ size ] } - ${ getMargin ( margin , "left" ) } - ${ getMargin ( margin , "right" ) } )`
255+ : sizes [ size ] ;
256+
257+ const DateInputContainer = styled . div < { margin : DateInputPropsType [ "margin" ] ; size : DateInputPropsType [ "size" ] } > `
258+ ${ ( props ) => props . size == "fillParent" && "width: 100%;" }
259+ display: flex;
260+ flex-direction: column;
261+ width: ${ ( props ) => calculateWidth ( props . margin , props . size ) } ;
262+ ${ ( props ) => props . size !== "fillParent" && "min-width:" + calculateWidth ( props . margin , props . size ) } ;
263+ margin: ${ ( props ) => ( props . margin && typeof props . margin !== "object" ? spaces [ props . margin ] : "0px" ) } ;
264+ margin-top: ${ ( props ) =>
265+ props . margin && typeof props . margin === "object" && props . margin . top ? spaces [ props . margin . top ] : "" } ;
266+ margin-right: ${ ( props ) =>
267+ props . margin && typeof props . margin === "object" && props . margin . right ? spaces [ props . margin . right ] : "" } ;
268+ margin-bottom: ${ ( props ) =>
269+ props . margin && typeof props . margin === "object" && props . margin . bottom ? spaces [ props . margin . bottom ] : "" } ;
270+ margin-left: ${ ( props ) =>
271+ props . margin && typeof props . margin === "object" && props . margin . left ? spaces [ props . margin . left ] : "" } ;
272+ font-family: ${ ( props ) => props . theme . textInput . fontFamily } ;
273+ ` ;
274+
275+ const Label = styled . label < {
276+ disabled : DateInputPropsType [ "disabled" ] ;
277+ hasHelperText : boolean ;
278+ } > `
279+ color: ${ ( props ) =>
280+ props . disabled ? props . theme . textInput . disabledLabelFontColor : props . theme . textInput . labelFontColor } ;
281+ font-size: ${ ( props ) => props . theme . textInput . labelFontSize } ;
282+ font-style: ${ ( props ) => props . theme . textInput . labelFontStyle } ;
283+ font-weight: ${ ( props ) => props . theme . textInput . labelFontWeight } ;
284+ line-height: ${ ( props ) => props . theme . textInput . labelLineHeight } ;
285+ ${ ( props ) => ! props . hasHelperText && `margin-bottom: 0.25rem` }
286+ ` ;
287+
288+ const OptionalLabel = styled . span `
289+ font-weight: ${ ( props ) => props . theme . textInput . optionalLabelFontWeight } ;
290+ ` ;
291+
292+ const HelperText = styled . span < { disabled : DateInputPropsType [ "disabled" ] } > `
293+ color: ${ ( props ) =>
294+ props . disabled ? props . theme . textInput . disabledHelperTextFontColor : props . theme . textInput . helperTextFontColor } ;
295+ font-size: ${ ( props ) => props . theme . textInput . helperTextFontSize } ;
296+ font-style: ${ ( props ) => props . theme . textInput . helperTextFontStyle } ;
297+ font-weight: ${ ( props ) => props . theme . textInput . helperTextFontWeight } ;
298+ line-height: ${ ( props ) => props . theme . textInput . helperTextLineHeight } ;
299+ margin-bottom: 0.25rem;
300+ ` ;
301+
211302const StyledPopoverContent = styled ( Popover . Content ) `
212303 z-index: 2147483647;
213304 &:focus-visible {
214305 outline: none;
215306 }
216307` ;
217308
218- const DateInputContainer = styled . div < { size : DateInputPropsType [ "size" ] } > `
219- ${ ( props ) => props . size == "fillParent" && "width: 100%;" }
220- ` ;
221-
222309export default DxcDateInput ;
0 commit comments