diff --git a/ui/v2.5/package.json b/ui/v2.5/package.json index 96538c877..7236dda18 100644 --- a/ui/v2.5/package.json +++ b/ui/v2.5/package.json @@ -49,6 +49,7 @@ "normalize-url": "^4.5.1", "react": "^17.0.2", "react-bootstrap": "^1.6.6", + "react-datepicker": "^4.10.0", "react-dom": "^17.0.2", "react-helmet": "^6.1.0", "react-intl": "^6.2.8", @@ -83,6 +84,7 @@ "@types/mousetrap": "^1.6.11", "@types/node": "^18.13.0", "@types/react": "^17.0.53", + "@types/react-datepicker": "^4.10.0", "@types/react-dom": "^17.0.19", "@types/react-helmet": "^6.1.6", "@types/react-router-bootstrap": "^0.24.5", diff --git a/ui/v2.5/src/components/Galleries/GalleryDetails/GalleryEditPanel.tsx b/ui/v2.5/src/components/Galleries/GalleryDetails/GalleryEditPanel.tsx index 5e7654d03..d560127dc 100644 --- a/ui/v2.5/src/components/Galleries/GalleryDetails/GalleryEditPanel.tsx +++ b/ui/v2.5/src/components/Galleries/GalleryDetails/GalleryEditPanel.tsx @@ -39,6 +39,7 @@ import { galleryTitle } from "src/core/galleries"; import { useRatingKeybinds } from "src/hooks/keybinds"; import { ConfigurationContext } from "src/hooks/Config"; import isEqual from "lodash-es/isEqual"; +import { DateInput } from "src/components/Shared/DateInput"; interface IProps { gallery: Partial; @@ -458,11 +459,18 @@ export const GalleryEditPanel: React.FC = ({ /> - {renderTextField( - "date", - intl.formatMessage({ id: "date" }), - "YYYY-MM-DD" - )} + + {FormUtils.renderLabel({ + title: intl.formatMessage({ id: "date" }), + })} + + formik.setFieldValue("date", value)} + error={formik.errors.date} + /> + + {FormUtils.renderLabel({ title: intl.formatMessage({ id: "rating" }), diff --git a/ui/v2.5/src/components/Images/ImageDetails/ImageEditPanel.tsx b/ui/v2.5/src/components/Images/ImageDetails/ImageEditPanel.tsx index 5255e2249..96ace1609 100644 --- a/ui/v2.5/src/components/Images/ImageDetails/ImageEditPanel.tsx +++ b/ui/v2.5/src/components/Images/ImageDetails/ImageEditPanel.tsx @@ -20,6 +20,7 @@ import { RatingSystem } from "src/components/Shared/Rating/RatingSystem"; import { useRatingKeybinds } from "src/hooks/keybinds"; import { ConfigurationContext } from "src/hooks/Config"; import isEqual from "lodash-es/isEqual"; +import { DateInput } from "src/components/Shared/DateInput"; interface IProps { image: GQL.ImageDataFragment; @@ -205,11 +206,18 @@ export const ImageEditPanel: React.FC = ({ /> - {renderTextField( - "date", - intl.formatMessage({ id: "date" }), - "YYYY-MM-DD" - )} + + {FormUtils.renderLabel({ + title: intl.formatMessage({ id: "date" }), + })} + + formik.setFieldValue("date", value)} + error={formik.errors.date} + /> + + {FormUtils.renderLabel({ title: intl.formatMessage({ id: "rating" }), diff --git a/ui/v2.5/src/components/List/Filters/DateFilter.tsx b/ui/v2.5/src/components/List/Filters/DateFilter.tsx index 44e38df36..bbedfdfee 100644 --- a/ui/v2.5/src/components/List/Filters/DateFilter.tsx +++ b/ui/v2.5/src/components/List/Filters/DateFilter.tsx @@ -4,6 +4,7 @@ import { useIntl } from "react-intl"; import { CriterionModifier } from "../../../core/generated-graphql"; import { IDateValue } from "../../../models/list-filter/types"; import { Criterion } from "../../../models/list-filter/criteria/criterion"; +import { DateInput } from "src/components/Shared/DateInput"; interface IDateFilterProps { criterion: Criterion; @@ -18,11 +19,7 @@ export const DateFilter: React.FC = ({ const { value } = criterion; - function onChanged( - event: React.ChangeEvent, - property: "value" | "value2" - ) { - const newValue = event.target.value; + function onChanged(newValue: string, property: "value" | "value2") { const valueCopy = { ...value }; valueCopy[property] = newValue; @@ -36,16 +33,10 @@ export const DateFilter: React.FC = ({ ) { equalsControl = ( - ) => - onChanged(e, "value") - } + onChanged(v, "value")} + placeholder={intl.formatMessage({ id: "criterion.value" })} /> ); @@ -59,17 +50,10 @@ export const DateFilter: React.FC = ({ ) { lowerControl = ( - ) => - onChanged(e, "value") - } + onChanged(v, "value")} + placeholder={intl.formatMessage({ id: "criterion.greater_than" })} /> ); @@ -83,25 +67,21 @@ export const DateFilter: React.FC = ({ ) { upperControl = ( - ) => - onChanged( - e, - criterion.modifier === CriterionModifier.LessThan - ? "value" - : "value2" - ) - } + + onChanged( + v, + criterion.modifier === CriterionModifier.LessThan + ? "value" + : "value2" + ) } + placeholder={intl.formatMessage({ id: "criterion.less_than" })} /> ); diff --git a/ui/v2.5/src/components/List/Filters/TimestampFilter.tsx b/ui/v2.5/src/components/List/Filters/TimestampFilter.tsx index eaf0351d8..1cb25b7d5 100644 --- a/ui/v2.5/src/components/List/Filters/TimestampFilter.tsx +++ b/ui/v2.5/src/components/List/Filters/TimestampFilter.tsx @@ -4,6 +4,7 @@ import { useIntl } from "react-intl"; import { CriterionModifier } from "../../../core/generated-graphql"; import { ITimestampValue } from "../../../models/list-filter/types"; import { Criterion } from "../../../models/list-filter/criteria/criterion"; +import { DateInput } from "src/components/Shared/DateInput"; interface ITimestampFilterProps { criterion: Criterion; @@ -18,11 +19,7 @@ export const TimestampFilter: React.FC = ({ const { value } = criterion; - function onChanged( - event: React.ChangeEvent, - property: "value" | "value2" - ) { - const newValue = event.target.value; + function onChanged(newValue: string, property: "value" | "value2") { const valueCopy = { ...value }; valueCopy[property] = newValue; @@ -36,7 +33,13 @@ export const TimestampFilter: React.FC = ({ ) { equalsControl = ( - onChanged(v, "value")} + placeholder={intl.formatMessage({ id: "criterion.value" })} + isTime + /> + {/* ) => @@ -47,7 +50,7 @@ export const TimestampFilter: React.FC = ({ intl.formatMessage({ id: "criterion.value" }) + " (YYYY-MM-DD HH:MM)" } - /> + /> */} ); } @@ -60,7 +63,13 @@ export const TimestampFilter: React.FC = ({ ) { lowerControl = ( - onChanged(v, "value")} + placeholder={intl.formatMessage({ id: "criterion.greater_than" })} + isTime + /> + {/* ) => @@ -71,7 +80,7 @@ export const TimestampFilter: React.FC = ({ intl.formatMessage({ id: "criterion.greater_than" }) + " (YYYY-MM-DD HH:MM)" } - /> + /> */} ); } @@ -84,7 +93,24 @@ export const TimestampFilter: React.FC = ({ ) { upperControl = ( - + onChanged( + v, + criterion.modifier === CriterionModifier.LessThan + ? "value" + : "value2" + ) + } + placeholder={intl.formatMessage({ id: "criterion.less_than" })} + isTime + /> + {/* ) => @@ -104,7 +130,7 @@ export const TimestampFilter: React.FC = ({ intl.formatMessage({ id: "criterion.less_than" }) + " (YYYY-MM-DD HH:MM)" } - /> + /> */} ); } diff --git a/ui/v2.5/src/components/Movies/MovieDetails/MovieEditPanel.tsx b/ui/v2.5/src/components/Movies/MovieDetails/MovieEditPanel.tsx index c7d910bb0..c2c51794c 100644 --- a/ui/v2.5/src/components/Movies/MovieDetails/MovieEditPanel.tsx +++ b/ui/v2.5/src/components/Movies/MovieDetails/MovieEditPanel.tsx @@ -24,6 +24,7 @@ import { MovieScrapeDialog } from "./MovieScrapeDialog"; import { useRatingKeybinds } from "src/hooks/keybinds"; import { ConfigurationContext } from "src/hooks/Config"; import isEqual from "lodash-es/isEqual"; +import { DateInput } from "src/components/Shared/DateInput"; interface IMovieEditPanel { movie: Partial; @@ -410,11 +411,18 @@ export const MovieEditPanel: React.FC = ({ - {renderTextField( - "date", - intl.formatMessage({ id: "date" }), - "YYYY-MM-DD" - )} + + {FormUtils.renderLabel({ + title: intl.formatMessage({ id: "date" }), + })} + + formik.setFieldValue("date", value)} + error={formik.errors.date} + /> + + {FormUtils.renderLabel({ diff --git a/ui/v2.5/src/components/Performers/PerformerDetails/PerformerEditPanel.tsx b/ui/v2.5/src/components/Performers/PerformerDetails/PerformerEditPanel.tsx index 8ee0b232a..b92330fad 100644 --- a/ui/v2.5/src/components/Performers/PerformerDetails/PerformerEditPanel.tsx +++ b/ui/v2.5/src/components/Performers/PerformerDetails/PerformerEditPanel.tsx @@ -43,6 +43,7 @@ import { } from "@fortawesome/free-solid-svg-icons"; import { StringListInput } from "src/components/Shared/StringListInput"; import isEqual from "lodash-es/isEqual"; +import { DateInput } from "src/components/Shared/DateInput"; const isScraper = ( scraper: GQL.Scraper | GQL.StashBox @@ -927,8 +928,35 @@ export const PerformerEditPanel: React.FC = ({ - {renderField("birthdate", { placeholder: "YYYY-MM-DD" })} - {renderField("death_date", { placeholder: "YYYY-MM-DD" })} + + + + + + + formik.setFieldValue("birthdate", value) + } + error={formik.errors.birthdate} + /> + + + + + + + + + + formik.setFieldValue("death_date", value) + } + error={formik.errors.death_date} + /> + + diff --git a/ui/v2.5/src/components/Scenes/SceneDetails/SceneEditPanel.tsx b/ui/v2.5/src/components/Scenes/SceneDetails/SceneEditPanel.tsx index 8a3f6a80a..d3e81aac0 100644 --- a/ui/v2.5/src/components/Scenes/SceneDetails/SceneEditPanel.tsx +++ b/ui/v2.5/src/components/Scenes/SceneDetails/SceneEditPanel.tsx @@ -52,6 +52,7 @@ import { galleryTitle } from "src/core/galleries"; import { useRatingKeybinds } from "src/hooks/keybinds"; import { lazyComponent } from "src/utils/lazyComponent"; import isEqual from "lodash-es/isEqual"; +import { DateInput } from "src/components/Shared/DateInput"; const SceneScrapeDialog = lazyComponent(() => import("./SceneScrapeDialog")); const SceneQueryModal = lazyComponent(() => import("./SceneQueryModal")); @@ -769,11 +770,20 @@ export const SceneEditPanel: React.FC = ({ /> - {renderTextField( - "date", - intl.formatMessage({ id: "date" }), - "YYYY-MM-DD" - )} + + + {FormUtils.renderLabel({ + title: intl.formatMessage({ id: "date" }), + })} + + formik.setFieldValue("date", value)} + error={formik.errors.date} + /> + + + {renderTextField( "director", intl.formatMessage({ id: "director" }) diff --git a/ui/v2.5/src/components/Shared/DateInput.tsx b/ui/v2.5/src/components/Shared/DateInput.tsx new file mode 100644 index 000000000..aa57fd37f --- /dev/null +++ b/ui/v2.5/src/components/Shared/DateInput.tsx @@ -0,0 +1,105 @@ +import { faCalendar } from "@fortawesome/free-regular-svg-icons"; +import React, { useMemo } from "react"; +import { Button, InputGroup, Form } from "react-bootstrap"; +import ReactDatePicker from "react-datepicker"; +import TextUtils from "src/utils/text"; +import { Icon } from "./Icon"; + +import "react-datepicker/dist/react-datepicker.css"; +import { useIntl } from "react-intl"; + +interface IProps { + disabled?: boolean; + value: string | undefined; + isTime?: boolean; + onValueChange(value: string): void; + placeholder?: string; + error?: string; +} + +export const DateInput: React.FC = (props: IProps) => { + const intl = useIntl(); + + const date = useMemo(() => { + const toDate = props.isTime + ? TextUtils.stringToFuzzyDateTime + : TextUtils.stringToFuzzyDate; + if (props.value) { + const ret = toDate(props.value); + if (!ret || isNaN(ret.getTime())) { + return undefined; + } + + return ret; + } + }, [props.value, props.isTime]); + + function maybeRenderButton() { + if (!props.disabled) { + const ShowPickerButton = ({ + onClick, + }: { + onClick: ( + event: React.MouseEvent + ) => void; + }) => ( + + ); + + const dateToString = props.isTime + ? TextUtils.dateTimeToString + : TextUtils.dateToString; + + return ( + { + props.onValueChange(v ? dateToString(v) : ""); + }} + customInput={React.createElement(ShowPickerButton)} + showMonthDropdown + showYearDropdown + scrollableMonthYearDropdown + scrollableYearDropdown + maxDate={new Date()} + yearDropdownItemNumber={100} + portalId="date-picker-portal" + showTimeSelect={props.isTime} + /> + ); + } + } + + const placeholderText = intl.formatMessage({ + id: props.isTime ? "datetime_format" : "date_format", + }); + + return ( +
+ + ) => + props.onValueChange(e.currentTarget.value) + } + placeholder={ + !props.disabled + ? props.placeholder + ? `${props.placeholder} (${placeholderText})` + : placeholderText + : undefined + } + isInvalid={!!props.error} + /> + {maybeRenderButton()} + + {props.error} + + +
+ ); +}; diff --git a/ui/v2.5/src/components/Shared/styles.scss b/ui/v2.5/src/components/Shared/styles.scss index ff1559a9b..2e63b4c75 100644 --- a/ui/v2.5/src/components/Shared/styles.scss +++ b/ui/v2.5/src/components/Shared/styles.scss @@ -323,3 +323,97 @@ button.collapse-button.btn-primary:not(:disabled):not(.disabled):active { } /* stylelint-enable */ } + +.date-input.form-control:focus { + // z-index gets set to 3 in input groups + z-index: inherit; +} + +/* stylelint-disable */ +div.react-datepicker { + background-color: $body-bg; + border-color: $card-bg; + color: $text-color; + + .react-datepicker__header, + .react-datepicker-time__header { + background-color: $secondary; + color: $text-color; + } + + .react-datepicker__day { + color: $text-color; + &.react-datepicker__day--disabled { + color: $text-muted; + } + + &:hover { + background: rgba(138, 155, 168, 0.15); + } + } + + div.react-datepicker__time-container div.react-datepicker__time { + background-color: $body-bg; + color: $text-color; + + ul.react-datepicker__time-list li.react-datepicker__time-list-item:hover { + background-color: rgba(138, 155, 168, 0.15); + } + } + + .react-datepicker__day-name { + color: $text-color; + } + + // replace the current month with the dropdowns + .react-datepicker__current-month { + display: none; + } + + .react-datepicker__triangle { + display: none; + } + + .react-datepicker__month-dropdown-container { + margin-left: 0; + margin-right: 0.25rem; + } + + .react-datepicker__year-dropdown-container { + margin-left: 0.25rem; + margin-right: 0; + } + + .react-datepicker__month-dropdown-container + .react-datepicker__month-read-view, + .react-datepicker__year-dropdown-container .react-datepicker__year-read-view { + font-weight: bold; + font-size: 0.944rem; + + // react-datepicker hides these fields when the dropdown is shown + visibility: visible !important; + } + + // hide the dropdown arrows + .react-datepicker__month-dropdown-container + .react-datepicker__month-read-view--down-arrow, + .react-datepicker__year-dropdown-container + .react-datepicker__year-read-view--down-arrow { + display: none; + } + + .react-datepicker__year-dropdown, + .react-datepicker__month-dropdown { + background-color: $body-bg; + + .react-datepicker__year-option:hover, + .react-datepicker__month-option:hover { + background-color: #8a9ba826; + } + } +} +/* stylelint-enable */ + +#date-picker-portal .react-datepicker-popper { + z-index: 1600; +} diff --git a/ui/v2.5/src/docs/en/Changelog/v0200.md b/ui/v2.5/src/docs/en/Changelog/v0200.md index 157fbe6eb..a8671f8f4 100644 --- a/ui/v2.5/src/docs/en/Changelog/v0200.md +++ b/ui/v2.5/src/docs/en/Changelog/v0200.md @@ -25,6 +25,7 @@ Once migrated, these files can be deleted. The files can be optionally deleted d * Added toggleable favorite button to Performer cards. ([#3369](https://github.com/stashapp/stash/pull/3369)) ### 🎨 Improvements +* Added date/time pickers for date and timestamp fields. ([#3572](https://github.com/stashapp/stash/pull/3572)) * Added folder browser to path filter UI. ([#3570](https://github.com/stashapp/stash/pull/3570)) * Include Organized flag in merge dialog. ([#3565](https://github.com/stashapp/stash/pull/3565)) * Scene cover generation is now optional during scanning, and can be generated using the Generate task. ([#3187](https://github.com/stashapp/stash/pull/3187)) diff --git a/ui/v2.5/src/locales/en-GB.json b/ui/v2.5/src/locales/en-GB.json index acdff64e8..71226e98d 100644 --- a/ui/v2.5/src/locales/en-GB.json +++ b/ui/v2.5/src/locales/en-GB.json @@ -728,6 +728,8 @@ "custom": "Custom", "date": "Date", "death_date": "Death Date", + "date_format": "YYYY-MM-DD", + "datetime_format": "YYYY-MM-DD HH:MM", "death_year": "Death Year", "descending": "Descending", "description": "Description", diff --git a/ui/v2.5/src/utils/text.ts b/ui/v2.5/src/utils/text.ts index df2fbf58a..a4150df91 100644 --- a/ui/v2.5/src/utils/text.ts +++ b/ui/v2.5/src/utils/text.ts @@ -189,6 +189,70 @@ const stringToDate = (dateString: string) => { return new Date(year, monthIndex, day, 0, 0, 0, 0); }; +const stringToFuzzyDate = (dateString: string) => { + if (!dateString) return null; + + const parts = dateString.split("-"); + // Invalid date string + let year = Number(parts[0]); + if (isNaN(year)) year = new Date().getFullYear(); + let monthIndex = 0; + if (parts.length > 1) { + monthIndex = Math.max(0, Number(parts[1]) - 1); + if (monthIndex > 11 || isNaN(monthIndex)) monthIndex = 0; + } + let day = 1; + if (parts.length > 2) { + day = Number(parts[2]); + if (day > 31 || isNaN(day)) day = 1; + } + + return new Date(year, monthIndex, day, 0, 0, 0, 0); +}; + +const stringToFuzzyDateTime = (dateString: string) => { + if (!dateString) return null; + + const dateTime = dateString.split(" "); + + let date: Date | null = null; + if (dateTime.length > 0) { + date = stringToFuzzyDate(dateTime[0]); + } + + if (!date) { + date = new Date(); + } + + if (dateTime.length > 1) { + const timeParts = dateTime[1].split(":"); + if (date && timeParts.length > 0) { + date.setHours(Number(timeParts[0])); + } + if (date && timeParts.length > 1) { + date.setMinutes(Number(timeParts[1])); + } + if (date && timeParts.length > 2) { + date.setSeconds(Number(timeParts[2])); + } + } + + return date; +}; + +function dateToString(date: Date) { + return `${date.getFullYear()}-${(date.getMonth() + 1) + .toString() + .padStart(2, "0")}-${date.getDate().toString().padStart(2, "0")}`; +} + +function dateTimeToString(date: Date) { + return `${dateToString(date)} ${date + .getHours() + .toString() + .padStart(2, "0")}:${date.getMinutes().toString().padStart(2, "0")}`; +} + const getAge = (dateString?: string | null, fromDateString?: string | null) => { if (!dateString) return 0; @@ -355,6 +419,10 @@ const TextUtils = { secondsToTimestamp, fileNameFromPath, stringToDate, + stringToFuzzyDate, + stringToFuzzyDateTime, + dateToString, + dateTimeToString, age: getAge, bitRate, resolution, diff --git a/ui/v2.5/yarn.lock b/ui/v2.5/yarn.lock index 21d2aad92..32d6db07d 100644 --- a/ui/v2.5/yarn.lock +++ b/ui/v2.5/yarn.lock @@ -2139,7 +2139,7 @@ tslib "^2.4.1" webcrypto-core "^1.7.4" -"@popperjs/core@^2.11.6": +"@popperjs/core@^2.11.6", "@popperjs/core@^2.9.2": version "2.11.6" resolved "https://registry.yarnpkg.com/@popperjs/core/-/core-2.11.6.tgz#cee20bd55e68a1720bdab363ecf0c821ded4cd45" integrity sha512-50/17A98tWUfQ176raKiOGXuYpLyyVMkxxG6oylzL3BPOlA6ADGdK7EYunSa4I064xerltq9TGXs8HmOk5E+vw== @@ -2339,6 +2339,16 @@ resolved "https://registry.yarnpkg.com/@types/prop-types/-/prop-types-15.7.5.tgz#5f19d2b85a98e9558036f6a3cacc8819420f05cf" integrity sha512-JCB8C6SnDoQf0cNycqd/35A7MjcnK+ZTqE7judS6o7utxUCg6imJg3QK2qzHKszlTjcj2cn+NwMB2i96ubpj7w== +"@types/react-datepicker@^4.10.0": + version "4.10.0" + resolved "https://registry.yarnpkg.com/@types/react-datepicker/-/react-datepicker-4.10.0.tgz#fcb0e6a7787491bf2f37fbda2b537062608a0056" + integrity sha512-Cq+ks20vBIU6XN67TbkCHu8M7V46Y6vJrKE2n+8q/GfueJyWWTIKeC3Z7cz/d+qxGDq/VCrqA929R0U4lNuztg== + dependencies: + "@popperjs/core" "^2.9.2" + "@types/react" "*" + date-fns "^2.0.1" + react-popper "^2.2.5" + "@types/react-dom@^17.0.19": version "17.0.19" resolved "https://registry.yarnpkg.com/@types/react-dom/-/react-dom-17.0.19.tgz#36feef3aa35d045cacd5ed60fe0eef5272f19492" @@ -3303,7 +3313,7 @@ chardet@^0.7.0: optionalDependencies: fsevents "~2.3.2" -classnames@^2.2.5, classnames@^2.3.1, classnames@^2.3.2: +classnames@^2.2.5, classnames@^2.2.6, classnames@^2.3.1, classnames@^2.3.2: version "2.3.2" resolved "https://registry.yarnpkg.com/classnames/-/classnames-2.3.2.tgz#351d813bf0137fcc6a76a16b88208d2560a0d924" integrity sha512-CSbhY4cFEJRe6/GQzIk5qXZ4Jeg5pcsP7b5peFSDpffpe1cqjASH/n9UTjBwOp6XpMSTwQ8Za2K5V02ueA7Tmw== @@ -3559,6 +3569,11 @@ dataloader@2.2.2: resolved "https://registry.yarnpkg.com/dataloader/-/dataloader-2.2.2.tgz#216dc509b5abe39d43a9b9d97e6e5e473dfbe3e0" integrity sha512-8YnDaaf7N3k/q5HnTJVuzSyLETjoZjVmHc4AeKAzOvKHEFQKcn64OKBfzHYtE9zGjctNM7V9I0MfnUVLpi7M5g== +date-fns@^2.0.1, date-fns@^2.24.0: + version "2.29.3" + resolved "https://registry.yarnpkg.com/date-fns/-/date-fns-2.29.3.tgz#27402d2fc67eb442b511b70bbdf98e6411cd68a8" + integrity sha512-dDCnyH2WnnKusqvZZ6+jA1O51Ibt8ZMRNkDZdyAyK4YfbDwa/cEmuztzG5pk6hqlp9aSBPYcjOlktquahGwGeA== + debounce@^1.2.0: version "1.2.1" resolved "https://registry.yarnpkg.com/debounce/-/debounce-1.2.1.tgz#38881d8f4166a5c5848020c11827b834bcb3e0a5" @@ -6449,6 +6464,18 @@ react-bootstrap@^1.6.6: uncontrollable "^7.2.1" warning "^4.0.3" +react-datepicker@^4.10.0: + version "4.10.0" + resolved "https://registry.yarnpkg.com/react-datepicker/-/react-datepicker-4.10.0.tgz#3f386ac5873dac5ea56544e51cdc01109938796c" + integrity sha512-6IfBCZyWj54ZZGLmEZJ9c4Yph0s9MVfEGDC2evOvf9AmVz+RRcfP2Czqad88Ff9wREbcbqa4dk7IFYeXF1d3Ag== + dependencies: + "@popperjs/core" "^2.9.2" + classnames "^2.2.6" + date-fns "^2.24.0" + prop-types "^15.7.2" + react-onclickoutside "^6.12.2" + react-popper "^2.3.0" + react-dom@^17.0.2: version "17.0.2" resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-17.0.2.tgz#ecffb6845e3ad8dbfcdc498f0d0a939736502c23" @@ -6463,6 +6490,11 @@ react-fast-compare@^2.0.1: resolved "https://registry.yarnpkg.com/react-fast-compare/-/react-fast-compare-2.0.4.tgz#e84b4d455b0fec113e0402c329352715196f81f9" integrity sha512-suNP+J1VU1MWFKcyt7RtjiSWUjvidmQSlqu+eHslq+342xCbGTYmC0mEhPCOHxlW0CywylOC1u2DFAT+bv4dBw== +react-fast-compare@^3.0.1: + version "3.2.1" + resolved "https://registry.yarnpkg.com/react-fast-compare/-/react-fast-compare-3.2.1.tgz#53933d9e14f364281d6cba24bfed7a4afb808b5f" + integrity sha512-xTYf9zFim2pEif/Fw16dBiXpe0hoy5PxcD8+OwBnTtNLfIm3g6WxhKNurY+6OmdH1u6Ta/W/Vl6vjbYP1MFnDg== + react-fast-compare@^3.1.1: version "3.2.0" resolved "https://registry.yarnpkg.com/react-fast-compare/-/react-fast-compare-3.2.0.tgz#641a9da81b6a6320f270e89724fb45a0b39e43bb" @@ -6504,6 +6536,11 @@ react-lifecycles-compat@^3.0.4: resolved "https://registry.yarnpkg.com/react-lifecycles-compat/-/react-lifecycles-compat-3.0.4.tgz#4f1a273afdfc8f3488a8c516bfda78f872352362" integrity sha512-fBASbA6LnOU9dOU2eW7aQ8xmYBSXUIWr+UmF9b1efZBazGNO+rcXT/icdKnYm2pTwcRylVUYwW7H1PHfLekVzA== +react-onclickoutside@^6.12.2: + version "6.12.2" + resolved "https://registry.yarnpkg.com/react-onclickoutside/-/react-onclickoutside-6.12.2.tgz#8e6cf80c7d17a79f2c908399918158a7b02dda01" + integrity sha512-NMXGa223OnsrGVp5dJHkuKxQ4czdLmXSp5jSV9OqiCky9LOpPATn3vLldc+q5fK3gKbEHvr7J1u0yhBh/xYkpA== + react-overlays@^5.1.2: version "5.2.1" resolved "https://registry.yarnpkg.com/react-overlays/-/react-overlays-5.2.1.tgz#49dc007321adb6784e1f212403f0fb37a74ab86b" @@ -6526,6 +6563,14 @@ react-photo-gallery@^8.0.0: prop-types "~15.7.2" resize-observer-polyfill "^1.5.0" +react-popper@^2.2.5, react-popper@^2.3.0: + version "2.3.0" + resolved "https://registry.yarnpkg.com/react-popper/-/react-popper-2.3.0.tgz#17891c620e1320dce318bad9fede46a5f71c70ba" + integrity sha512-e1hj8lL3uM+sgSR4Lxzn5h1GxBlpa4CQz0XLF8kx4MDrDRWY0Ena4c97PUeSX9i5W3UAfDP0z0FXCTQkoXUl3Q== + dependencies: + react-fast-compare "^3.0.1" + warning "^4.0.2" + react-refresh@^0.14.0: version "0.14.0" resolved "https://registry.yarnpkg.com/react-refresh/-/react-refresh-0.14.0.tgz#4e02825378a5f227079554d4284889354e5f553e" @@ -7957,7 +8002,7 @@ vite@^4.1.1: optionalDependencies: fsevents "~2.3.2" -warning@^4.0.0, warning@^4.0.3: +warning@^4.0.0, warning@^4.0.2, warning@^4.0.3: version "4.0.3" resolved "https://registry.yarnpkg.com/warning/-/warning-4.0.3.tgz#16e9e077eb8a86d6af7d64aa1e05fd85b4678ca3" integrity sha512-rpJyN222KWIvHJ/F53XSZv0Zl/accqHR8et1kpaMTD/fLCRxtV8iX8czMzY7sVZupTI3zcUTg8eycS2kNF9l6w==