J'ai rencontré exactement le même problème il y a quelques semaines. Je cherchais une solution facile mais je n'en ai pas trouvé. Ce que j'ai fait est de diviser le Datepicker en 2 composants (+ redux).
- Est-ce que votre contribution
<Datepicker />
- Est-ce que votre sélecteur de date flottant
<DatepickerFloatingItem/>
Afficheur de date
Le sélecteur de date est simplement le champ d'entrée et c'est le composant que vous pouvez utiliser dans toute votre application web react. Le plus grand changement ici est que vous devez exécuter une action avec le dispatcher pour afficher l'élément flottant. De plus, vous devez déterminer les coordonnées X et Y de votre Datepicker pour placer l'élément flottant au bon endroit.
Voici à quoi pourrait ressembler le composant Datepicker (j'ai supprimé le code logique pour ne pas embrouiller tout le monde) :
class DatetimePicker extends React.Component<IDatetimePickerProps, IDatetimePickerState> {
public componentDidMount() {
this.updateDate(this.props.date);
window.addEventListener("resize", this.updateDimensions);
}
public componentWillUnmount() {
// display the floating datepicker even when resizing the window
window.removeEventListener("resize", this.updateDimensions);
}
///
/// LOGIC CODE (deleted)
///
private showCalendar = (): void => {
const { date, key } = this.state;
const { dispatch } = this.props;
const { showFloating } = this.props.datePickerState
if (!showFloating) {
this.createOutsideClickEvent();
if (date) {
this.updateDate(date);
}
} else {
this.removeOutsideClickEvent();
}
var boundingRect = document.getElementById(key)?.getBoundingClientRect();
if (boundingRect) {
dispatch(updateDatepickerData({ updateDate: this.updateDate, showFloating: true, date, positionX: boundingRect.left, positionY: boundingRect.top + boundingRect.height, key }))
}
}
private updateDimensions = (): void => {
const { dispatch } = this.props;
const { date, showFloating } = this.props.datePickerState;
const { key } = this.state;
var boundingRect = document.getElementById(key)?.getBoundingClientRect();
if (boundingRect && this.props.datePickerState.key === key) {
dispatch(updateDatepickerData({ positionX: boundingRect.left, positionY: boundingRect.top + boundingRect.height, date, showFloating }))
}
}
public render(): React.ReactNode {
const { input, wrapperRef, key } = this.state;
const { style, disabled, className, onClick, styleWrapper, icon } = this.props;
return <span className="datetimepicker" ref={wrapperRef} onClick={onClick} style={styleWrapper}>
<Input
className={`datetimepicker__input ${className}`}
value={input}
onChange={this.updateInput}
getFocus={this.disableErrorView}
getBlur={this.textInputLostFocus}
rightButtonClicked={this.showCalendar}
style={style}
id={key}
icon={icon}
disabled={disabled}
/>
</span>
}
}
DatepickerFloatingItem
Vous ne devez positionner le DatepickerFloatingItem qu'une seule fois dans votre application. Il est préférable de le positionner dans App.js (le composant racine).
Il est également important d'avoir position: relative
pour l'élément parent et définir position: fixed
pour le DatepickerFloatingItem. Maintenant, vous pouvez facilement positionner votre élément flottant en utilisant top:
y left:
avec les coordonnées du Datepicker
Et voici à quoi pourrait ressembler le DatepickerFloatingItem (j'ai également supprimé le code inutile pour le rendre plus compréhensible)
interface IDatepickerFloatingItemStateProps {
date: Date
showFloating: boolean
positionX?: number
positionY?: number
}
class DatepickerFloatingItem extends React.Component<IDatepickerFloatingItemProps, IDatepickerFloatingItemState> {
private clickedFloatingDatepicker = (event: React.MouseEvent<HTMLSpanElement>): void => event.preventDefault()
private updateDate = (date?: Date, closeFloating?: boolean): void => {
const { dispatch } = this.props;
dispatch(updateDatepickerDate(date ? date : new Date()))
if (closeFloating) dispatch(updateDatepickerShowFloating(!closeFloating))
}
public render(): React.ReactNode {
const { showFloating, date, positionX, positionY } = this.props;
const { datepickerView } = this.state;
return <span className={`datetimepicker__floating ${showFloating ? "show" : ""}`} onClick={this.clickedFloatingDatepicker} style={{ top: `${positionY}px`, left: `${positionX}px` }}>
<DateCalendar datepickerView={datepickerView} date={date} updateDate={this.updateDate} />
</span>
}
}
function mapStateToProps(applicationState: ApplicationState): IDatepickerFloatingItemStateProps {
return {
date: applicationState.datepicker.date,
showFloating: applicationState.datepicker.showFloating,
positionX: applicationState.datepicker.positionX,
positionY: applicationState.datepicker.positionY
}
}
export default connect(mapStateToProps)(DatepickerFloatingItem)
Redux
J'ai dû déplacer certaines choses vers le magasin Redux pour m'assurer que les FloatingDatePicker ainsi que le Afficheur de date avoir la possibilité de communiquer d'une manière ou d'une autre
J'ai gardé le magasin redux assez simple :
import { Action, Reducer } from 'redux';
export interface DatepickerState {
date: Date
showFloating: boolean
positionX?: number
positionY?: number
updateDate?: (date?: Date) => void
}
export const UPDATE_DATEPICKER_SHOWFLOATING = "UPDATE_DATEPICKER_SHOWFLOATING";
export const UPDATE_DATEPICKER_DATA = "UPDATE_DATEPICKER_DATA";
export const UPDATE_DATEPICKER_DATE = "UPDATE_DATEPICKER_DATE";
export interface UpdateDatepickerShowFloating {
type: "UPDATE_DATEPICKER_SHOWFLOATING"
showFloating: boolean
}
export interface UpdateDatepickerDate {
type: "UPDATE_DATEPICKER_DATE"
date: Date
}
export interface UpdateDatepickerData {
type: "UPDATE_DATEPICKER_DATA"
state: DatepickerState
}
type KnownAction = UpdateDatepickerShowFloating | UpdateDatepickerData | UpdateDatepickerDate
const unloadedState: DatepickerState = { updateDate: () => { }, date: new Date(), showFloating: false, showTime: false, positionX: 0, positionY: 0 }
export const reducer: Reducer<DatepickerState> = (state: DatepickerState | undefined, incomingAction: Action): DatepickerState => {
if (state === undefined) {
return unloadedState;
}
const action = incomingAction as KnownAction;
switch (action.type) {
case UPDATE_DATEPICKER_SHOWFLOATING:
return { ...state, showFloating: action.showFloating }
case UPDATE_DATEPICKER_DATE:
setTimeout(() => { if (state.updateDate) state.updateDate(action.date) }, 1)
return { ...state, date: action.date }
case UPDATE_DATEPICKER_DATA:
return { ...state, ...action.state }
default:
break;
}
return state;
}
Et comme vous pouvez le voir sur l'image, il fonctionne en fait à l'intérieur d'une modale :
Je sais que cette approche prend beaucoup de temps, mais j'espère quand même qu'elle vous a aidé d'une manière ou d'une autre et j'espère aussi que vos yeux ne brûlent pas à force de voir des composants basés sur des classes.