diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..f3e0823 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,6 @@ +{ + // IntelliSense for taiwind variants + "tailwindCSS.experimental.classRegex": [ + ["tv\\((([^()]*|\\([^()]*\\))*)\\)", "[\"'`]([^\"'`]*).*?[\"'`]"] + ] +} diff --git a/packages/frontend/package.json b/packages/frontend/package.json index a2a77ba..4973668 100644 --- a/packages/frontend/package.json +++ b/packages/frontend/package.json @@ -13,13 +13,14 @@ "@types/react": "^18.2.42", "@types/react-dom": "^18.2.17", "assert": "^2.1.0", - "date-fns": "^3.0.1", + "date-fns": "^3.3.1", "downshift": "^8.2.3", "eslint-config-react-app": "^7.0.1", "gql-client": "^1.0.0", "luxon": "^3.4.4", "octokit": "^3.1.2", "react": "^18.2.0", + "react-calendar": "^4.8.0", "react-code-blocks": "^0.1.6", "react-day-picker": "^8.9.1", "react-dom": "^18.2.0", diff --git a/packages/frontend/src/components/shared/Calendar/Calendar.css b/packages/frontend/src/components/shared/Calendar/Calendar.css new file mode 100644 index 0000000..85ce418 --- /dev/null +++ b/packages/frontend/src/components/shared/Calendar/Calendar.css @@ -0,0 +1,128 @@ +/* React Calendar */ +.react-calendar { + @apply border-none font-sans; +} + +/* Weekdays -- START */ +.react-calendar__month-view__weekdays { + @apply p-0 flex items-center justify-center; +} + +.react-calendar__month-view__weekdays__weekday { + @apply h-8 w-12 flex items-center justify-center p-0 font-medium text-xs text-elements-disabled mb-2; +} + +abbr[title] { + text-decoration: none; +} +/* Weekdays -- END */ + +/* Days -- START */ +.react-calendar__month-view__days { + @apply p-0 gap-0; +} + +.react-calendar__month-view__days__day--neighboringMonth { + @apply !text-elements-disabled; +} + +.react-calendar__month-view__days__day--neighboringMonth:hover { + @apply !text-elements-disabled !bg-transparent; +} + +.react-calendar__month-view__days__day--neighboringMonth { + @apply !text-elements-disabled !bg-transparent; +} + +/* For weekend days */ +.react-calendar__month-view__days__day--weekend { + /* color: ${colors.grey[950]} !important; */ +} + +.react-calendar__tile { + @apply h-12 w-12 text-elements-high-em; +} + +.react-calendar__tile:hover { + @apply bg-base-bg-emphasized rounded-lg; +} + +.react-calendar__tile:focus-visible { + @apply bg-base-bg-emphasized rounded-lg focus-ring z-10; +} + +.react-calendar__tile--now { + @apply bg-base-bg-emphasized text-elements-high-em rounded-lg; +} + +.react-calendar__tile--now:hover { + @apply bg-base-bg-emphasized text-elements-high-em rounded-lg; +} + +.react-calendar__tile--now:focus-visible { + @apply bg-base-bg-emphasized text-elements-high-em rounded-lg focus-ring; +} + +.react-calendar__tile--active { + @apply bg-controls-primary text-elements-on-primary rounded-lg; +} + +.react-calendar__tile--active:hover { + @apply bg-controls-primary-hovered; +} + +.react-calendar__tile--active:focus-visible { + @apply bg-controls-primary-hovered focus-ring; +} + +/* Range -- START */ +.react-calendar__tile--range { + @apply bg-controls-secondary text-elements-on-secondary rounded-none; +} + +.react-calendar__tile--range:hover { + @apply bg-controls-secondary-hovered text-elements-on-secondary rounded-none; +} + +.react-calendar__tile--range:focus-visible { + @apply bg-controls-secondary-hovered text-elements-on-secondary rounded-lg; +} + +.react-calendar__tile--rangeStart { + @apply bg-controls-primary text-elements-on-primary rounded-lg; +} + +.react-calendar__tile--rangeStart:hover { + @apply bg-controls-primary-hovered text-elements-on-primary rounded-lg; +} + +.react-calendar__tile--rangeStart:focus-visible { + @apply bg-controls-primary-hovered text-elements-on-primary rounded-lg focus-ring; +} + +.react-calendar__tile--rangeEnd { + @apply bg-controls-primary text-elements-on-primary rounded-lg; +} + +.react-calendar__tile--rangeEnd:hover { + @apply bg-controls-primary-hovered text-elements-on-primary rounded-lg; +} + +.react-calendar__tile--rangeEnd:focus-visible { + @apply bg-controls-primary-hovered text-elements-on-primary rounded-lg focus-ring; +} +/* Range -- END */ +/* Days -- END */ + +/* Months -- START */ +.react-calendar__tile--hasActive { + @apply bg-controls-primary text-elements-on-primary rounded-lg; +} + +.react-calendar__tile--hasActive:hover { + @apply bg-controls-primary-hovered text-elements-on-primary rounded-lg; +} + +.react-calendar__tile--hasActive:focus-visible { + @apply bg-controls-primary-hovered text-elements-on-primary rounded-lg focus-ring; +} diff --git a/packages/frontend/src/components/shared/Calendar/Calendar.theme.ts b/packages/frontend/src/components/shared/Calendar/Calendar.theme.ts new file mode 100644 index 0000000..ce6e93d --- /dev/null +++ b/packages/frontend/src/components/shared/Calendar/Calendar.theme.ts @@ -0,0 +1,49 @@ +import { VariantProps, tv } from 'tailwind-variants'; + +export const calendarTheme = tv({ + slots: { + wrapper: [ + 'max-w-[352px]', + 'bg-surface-floating', + 'shadow-calendar', + 'rounded-xl', + ], + calendar: ['flex', 'flex-col', 'py-2', 'px-2', 'gap-2'], + navigation: [ + 'flex', + 'items-center', + 'justify-between', + 'gap-3', + 'py-2.5', + 'px-1', + ], + actions: ['flex', 'items-center', 'justify-center', 'gap-1.5', 'flex-1'], + button: [ + 'flex', + 'items-center', + 'gap-2', + 'px-2', + 'py-2', + 'rounded-lg', + 'border', + 'border-border-interactive', + 'text-elements-high-em', + 'shadow-field', + 'bg-white', + 'hover:bg-base-bg-alternate', + 'focus-visible:bg-base-bg-alternate', + ], + footer: [ + 'flex', + 'items-center', + 'justify-end', + 'py-3', + 'px-2', + 'gap-3', + 'border-t', + 'border-border-separator', + ], + }, +}); + +export type CalendarTheme = VariantProps; diff --git a/packages/frontend/src/components/shared/Calendar/Calendar.tsx b/packages/frontend/src/components/shared/Calendar/Calendar.tsx new file mode 100644 index 0000000..dfc15e3 --- /dev/null +++ b/packages/frontend/src/components/shared/Calendar/Calendar.tsx @@ -0,0 +1,298 @@ +import React, { + ComponentPropsWithRef, + MouseEvent, + ReactNode, + useCallback, + useState, +} from 'react'; +import { + Calendar as ReactCalendar, + CalendarProps as ReactCalendarProps, +} from 'react-calendar'; +import { Value } from 'react-calendar/dist/cjs/shared/types'; +import { CalendarTheme, calendarTheme } from './Calendar.theme'; +import { Button } from 'components/shared/Button'; +import { + ChevronGrabberHorizontal, + ChevronLeft, + ChevronRight, +} from 'components/shared/CustomIcon'; + +import './Calendar.css'; +import { format } from 'date-fns'; + +const CALENDAR_VIEW = ['month', 'year', 'decade'] as const; +export type CalendarView = (typeof CALENDAR_VIEW)[number]; + +/** + * Defines a custom set of props for a React calendar component by excluding specific props + * from the original ReactCalendarProps type. + * @type {CustomReactCalendarProps} + */ +type CustomReactCalendarProps = Omit< + ReactCalendarProps, + 'view' | 'showNavigation' | 'onClickMonth' | 'onClickYear' +>; + +export interface CalendarProps extends CustomReactCalendarProps, CalendarTheme { + /** + * Optional props for wrapping a component with a div element. + */ + wrapperProps?: ComponentPropsWithRef<'div'>; + /** + * Props for the calendar wrapper component. + */ + calendarWrapperProps?: ComponentPropsWithRef<'div'>; + /** + * Optional props for the footer component. + */ + footerProps?: ComponentPropsWithRef<'div'>; + /** + * Optional custom actions to be rendered. + */ + actions?: ReactNode; + /** + * Optional callback function that is called when a value is selected. + * @param {Value} value - The selected value + * @returns None + */ + onSelect?: (value: Value) => void; + /** + * Optional callback function that is called when a cancel action is triggered. + * @returns None + */ + onCancel?: () => void; +} + +/** + * Calendar component that allows users to select dates and navigate through months and years. + * @param {Object} CalendarProps - Props for the Calendar component. + * @returns {JSX.Element} A calendar component with navigation, date selection, and actions. + */ +export const Calendar = ({ + selectRange, + activeStartDate: activeStartDateProp, + value: valueProp, + wrapperProps, + calendarWrapperProps, + footerProps, + actions, + onSelect, + onCancel, + onChange: onChangeProp, + ...props +}: CalendarProps): JSX.Element => { + const { + wrapper, + calendar, + navigation, + actions: actionsClass, + button, + footer, + } = calendarTheme(); + + const today = new Date(); + const currentMonth = format(today, 'MMM'); + const currentYear = format(today, 'yyyy'); + + const [view, setView] = useState('month'); + const [activeDate, setActiveDate] = useState( + activeStartDateProp ?? today, + ); + const [value, setValue] = useState(valueProp as Value); + const [month, setMonth] = useState(currentMonth); + const [year, setYear] = useState(currentYear); + + /** + * Update the navigation label based on the active date + */ + const changeNavigationLabel = useCallback( + (date: Date) => { + setMonth(format(date, 'MMM')); + setYear(format(date, 'yyyy')); + }, + [setMonth, setYear], + ); + + /** + * Change the active date base on the action and range + */ + const handleNavigate = useCallback( + (action: 'previous' | 'next', view: CalendarView) => { + setActiveDate((date) => { + const newDate = new Date(date); + switch (view) { + case 'month': + newDate.setMonth( + action === 'previous' ? date.getMonth() - 1 : date.getMonth() + 1, + ); + break; + case 'year': + newDate.setFullYear( + action === 'previous' + ? date.getFullYear() - 1 + : date.getFullYear() + 1, + ); + break; + case 'decade': + newDate.setFullYear( + action === 'previous' + ? date.getFullYear() - 10 + : date.getFullYear() + 10, + ); + break; + } + changeNavigationLabel(newDate); + return newDate; + }); + }, + [setActiveDate, changeNavigationLabel], + ); + + /** + * Change the view of the calendar + */ + const handleChangeView = useCallback( + (view: CalendarView) => { + setView(view); + }, + [setView], + ); + + /** + * Change the active date and set the view to the selected type + * and also update the navigation label + */ + const handleChangeNavigation = useCallback( + (view: 'month' | 'year', date: Date) => { + setActiveDate(date); + changeNavigationLabel(date); + setView(view); + }, + [setActiveDate, changeNavigationLabel, setView], + ); + + const handlePrevious = useCallback(() => { + switch (view) { + case 'month': + return handleNavigate('previous', 'month'); + case 'year': + return handleNavigate('previous', 'year'); + case 'decade': + return handleNavigate('previous', 'decade'); + } + }, [view]); + + const handleNext = useCallback(() => { + switch (view) { + case 'month': + return handleNavigate('next', 'month'); + case 'year': + return handleNavigate('next', 'year'); + case 'decade': + return handleNavigate('next', 'decade'); + } + }, [view]); + + const handleChange = useCallback( + (newValue: Value, event: MouseEvent) => { + setValue(newValue); + + // Call the onChange prop if it exists + onChangeProp?.(newValue, event); + + /** + * Update the active date and navigation label + * + * NOTE: + * For range selection, the active date is not updated + * The user only can select multiple dates within the same month + */ + if (!selectRange) { + setActiveDate(newValue as Date); + changeNavigationLabel(newValue as Date); + } + }, + [setValue, setActiveDate, changeNavigationLabel, selectRange], + ); + + return ( +
+ {/* Calendar wrapper */} +
+ {/* Navigation */} +
+ +
+ + +
+ +
+ + {/* Calendar */} + handleChangeNavigation('month', date)} + onClickYear={(date) => handleChangeNavigation('year', date)} + /> +
+ + {/* Footer or CTA */} +
+ {actions ? ( + actions + ) : ( + <> + + + + )} +
+
+ ); +}; diff --git a/packages/frontend/src/components/shared/Calendar/index.ts b/packages/frontend/src/components/shared/Calendar/index.ts new file mode 100644 index 0000000..a723380 --- /dev/null +++ b/packages/frontend/src/components/shared/Calendar/index.ts @@ -0,0 +1 @@ +export * from './Calendar'; diff --git a/packages/frontend/src/components/shared/CustomIcon/ChevronGrabberHorizontal.tsx b/packages/frontend/src/components/shared/CustomIcon/ChevronGrabberHorizontal.tsx new file mode 100644 index 0000000..b62f79d --- /dev/null +++ b/packages/frontend/src/components/shared/CustomIcon/ChevronGrabberHorizontal.tsx @@ -0,0 +1,20 @@ +import React from 'react'; +import { CustomIcon, CustomIconProps } from './CustomIcon'; + +export const ChevronGrabberHorizontal = (props: CustomIconProps) => { + return ( + + + + ); +}; diff --git a/packages/frontend/src/components/shared/CustomIcon/ChevronLeft.tsx b/packages/frontend/src/components/shared/CustomIcon/ChevronLeft.tsx new file mode 100644 index 0000000..f9c4885 --- /dev/null +++ b/packages/frontend/src/components/shared/CustomIcon/ChevronLeft.tsx @@ -0,0 +1,21 @@ +import React from 'react'; +import { CustomIcon, CustomIconProps } from './CustomIcon'; + +export const ChevronLeft = (props: CustomIconProps) => { + return ( + + + + ); +}; diff --git a/packages/frontend/src/components/shared/CustomIcon/ChevronRight.tsx b/packages/frontend/src/components/shared/CustomIcon/ChevronRight.tsx new file mode 100644 index 0000000..75da005 --- /dev/null +++ b/packages/frontend/src/components/shared/CustomIcon/ChevronRight.tsx @@ -0,0 +1,21 @@ +import React from 'react'; +import { CustomIcon, CustomIconProps } from './CustomIcon'; + +export const ChevronRight = (props: CustomIconProps) => { + return ( + + + + ); +}; diff --git a/packages/frontend/src/components/shared/CustomIcon/index.ts b/packages/frontend/src/components/shared/CustomIcon/index.ts index d50d942..e4b5103 100644 --- a/packages/frontend/src/components/shared/CustomIcon/index.ts +++ b/packages/frontend/src/components/shared/CustomIcon/index.ts @@ -1,2 +1,5 @@ export * from './PlusIcon'; export * from './CustomIcon'; +export * from './ChevronGrabberHorizontal'; +export * from './ChevronLeft'; +export * from './ChevronRight'; diff --git a/packages/frontend/src/pages/components/index.tsx b/packages/frontend/src/pages/components/index.tsx index 8c23f34..775acbb 100644 --- a/packages/frontend/src/pages/components/index.tsx +++ b/packages/frontend/src/pages/components/index.tsx @@ -1,8 +1,13 @@ import { Button, ButtonOrLinkProps } from 'components/shared/Button'; +import { Calendar } from 'components/shared/Calendar'; import { PlusIcon } from 'components/shared/CustomIcon'; -import React from 'react'; +import React, { useState } from 'react'; +import { Value } from 'react-calendar/dist/cjs/shared/types'; const Page = () => { + const [singleDate, setSingleDate] = useState(); + const [dateRange, setDateRange] = useState(); + return (
@@ -13,6 +18,7 @@ const Page = () => { packages/frontend/src/pages/components/index.tsx

+
{/* Insert Components here */} @@ -69,6 +75,31 @@ const Page = () => { ))}
+ +
+
+

Calendar

+
+
+

Selected date: {singleDate?.toString()}

+ +
+
+

+ Start date:{' '} + {dateRange instanceof Array ? dateRange[0]?.toString() : ''}{' '} +
+ End date:{' '} + {dateRange instanceof Array ? dateRange[1]?.toString() : ''} +

+ +
+
+
); diff --git a/packages/frontend/tailwind.config.js b/packages/frontend/tailwind.config.js index 92ee77d..00ce21f 100644 --- a/packages/frontend/tailwind.config.js +++ b/packages/frontend/tailwind.config.js @@ -144,6 +144,9 @@ export default withMT({ boxShadow: { button: 'inset 0px 0px 4px rgba(255, 255, 255, 0.25), inset 0px -2px 0px rgba(0, 0, 0, 0.04)', + calendar: + '0px 3px 20px rgba(8, 47, 86, 0.1), 0px 0px 4px rgba(8, 47, 86, 0.14)', + field: '0px 1px 2px rgba(0, 0, 0, 0.04)', }, spacing: { 2.5: '0.625rem', diff --git a/yarn.lock b/yarn.lock index e471349..8845c97 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3910,6 +3910,18 @@ dependencies: "@types/node" "*" +"@types/lodash.memoize@^4.1.7": + version "4.1.9" + resolved "https://registry.yarnpkg.com/@types/lodash.memoize/-/lodash.memoize-4.1.9.tgz#9f8912d39b6e450c0d342a2b74c99d331bf2016b" + integrity sha512-glY1nQuoqX4Ft8Uk+KfJudOD7DQbbEDF6k9XpGncaohW3RW4eSWBlx6AA0fZCrh40tZcQNH4jS/Oc59J6Eq+aw== + dependencies: + "@types/lodash" "*" + +"@types/lodash@*": + version "4.14.202" + resolved "https://registry.yarnpkg.com/@types/lodash/-/lodash-4.14.202.tgz#f09dbd2fb082d507178b2f2a5c7e74bd72ff98f8" + integrity sha512-OvlIYQK9tNneDlS0VN54LLd5uiPCBOp7gS5Z0f1mjoJYBrtStzgmJBxONW3U6OZqdtNzZPmn9BS/7WI7BFFcFQ== + "@types/long@^4.0.0", "@types/long@^4.0.1": version "4.0.2" resolved "https://registry.yarnpkg.com/@types/long/-/long-4.0.2.tgz#b74129719fc8d11c01868010082d483b7545591a" @@ -4687,6 +4699,11 @@ "@webassemblyjs/ast" "1.11.6" "@xtuc/long" "4.2.2" +"@wojtekmaj/date-utils@^1.1.3": + version "1.5.1" + resolved "https://registry.yarnpkg.com/@wojtekmaj/date-utils/-/date-utils-1.5.1.tgz#c3cd67177ac781cfa5736219d702a55a2aea5f2b" + integrity sha512-+i7+JmNiE/3c9FKxzWFi2IjRJ+KzZl1QPu6QNrsgaa2MuBgXvUy4gA1TVzf/JMdIIloB76xSKikTWuyYAIVLww== + "@wry/caches@^1.0.0": version "1.0.1" resolved "https://registry.yarnpkg.com/@wry/caches/-/caches-1.0.1.tgz#8641fd3b6e09230b86ce8b93558d44cf1ece7e52" @@ -6144,6 +6161,11 @@ clone@^1.0.2: resolved "https://registry.yarnpkg.com/clone/-/clone-1.0.4.tgz#da309cc263df15994c688ca902179ca3c7cd7c7e" integrity sha512-JQHZ2QMW6l3aH/j6xCqQThY/9OH4D/9ls34cgkUBiEeocRTU04tHfKPBsUK1PqZCUQM7GiA0IIXJSuXHI64Kbg== +clsx@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/clsx/-/clsx-2.1.0.tgz#e851283bcb5c80ee7608db18487433f7b23f77cb" + integrity sha512-m3iNNWpd9rl3jvvcBnu70ylMdrXt8Vlq4HYadnU5fwcOtvkSQWPmj7amUcDT2qYI7risszBjI5AUIUox9D16pg== + cmd-shim@6.0.1: version "6.0.1" resolved "https://registry.yarnpkg.com/cmd-shim/-/cmd-shim-6.0.1.tgz#a65878080548e1dca760b3aea1e21ed05194da9d" @@ -6821,10 +6843,10 @@ data-urls@^2.0.0: whatwg-mimetype "^2.3.0" whatwg-url "^8.0.0" -date-fns@^3.0.1: - version "3.0.1" - resolved "https://registry.yarnpkg.com/date-fns/-/date-fns-3.0.1.tgz#a95b3e8296e72cf57c99819f37679aa27ca65ec4" - integrity sha512-cr9igCUa0QSqgAMj7JOrYTY6Nh1rmyGrFDko7ADqfmaQqP/I2N4rlfrLl7AWuzDaoIpz6MNjoEcTPzgZYIrhnA== +date-fns@^3.3.1: + version "3.3.1" + resolved "https://registry.yarnpkg.com/date-fns/-/date-fns-3.3.1.tgz#7581daca0892d139736697717a168afbb908cfed" + integrity sha512-y8e109LYGgoQDveiEBD3DYXKba1jWf5BA8YU1FL5Tvm0BTdEfy54WLCwnuYWZNnzzvALy/QQ4Hov+Q9RVRv+Zw== dateformat@^3.0.3: version "3.0.3" @@ -8720,6 +8742,14 @@ get-tsconfig@^4.7.0: dependencies: resolve-pkg-maps "^1.0.0" +get-user-locale@^2.2.1: + version "2.3.1" + resolved "https://registry.yarnpkg.com/get-user-locale/-/get-user-locale-2.3.1.tgz#fc7319429c8a70fac01b3b2a0b08b0c71c1d3fe2" + integrity sha512-VEvcsqKYx7zhZYC1CjecrDC5ziPSpl1gSm0qFFJhHSGDrSC+x4+p1KojWC/83QX//j476gFhkVXP/kNUc9q+bQ== + dependencies: + "@types/lodash.memoize" "^4.1.7" + lodash.memoize "^4.1.1" + git-raw-commits@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/git-raw-commits/-/git-raw-commits-3.0.0.tgz#5432f053a9744f67e8db03dbc48add81252cfdeb" @@ -11012,7 +11042,7 @@ lodash.isstring@^4.0.1: resolved "https://registry.yarnpkg.com/lodash.isstring/-/lodash.isstring-4.0.1.tgz#d527dfb5456eca7cc9bb95d5daeaf88ba54a5451" integrity sha512-0wJxfxH1wgO3GrbuP+dTTk7op+6L41QCXbGINEmD+ny/G/eCqGzxyCsh7159S+mgDDcoarnBw6PC1PS5+wUGgw== -lodash.memoize@^4.1.2: +lodash.memoize@^4.1.1, lodash.memoize@^4.1.2: version "4.1.2" resolved "https://registry.npmjs.org/lodash.memoize/-/lodash.memoize-4.1.2.tgz" integrity sha512-t7j+NzmgnQzTAYXcsHYLgimltOV1MXHtlOWf6GjL9Kj8GK5FInw5JotxvbOs+IvV1/Dzo04/fCGfLVs7aXb4Ag== @@ -11065,7 +11095,7 @@ long@^5.2.0: resolved "https://registry.yarnpkg.com/long/-/long-5.2.3.tgz#a3ba97f3877cf1d778eccbcb048525ebb77499e1" integrity sha512-lcHwpNoggQTObv5apGNCTdJrO69eHOZMi4BNC+rTLER8iHAqGrUVeLh/irVIM7zTw2bOXA8T6uNPeujwOLg/2Q== -loose-envify@^1.1.0, loose-envify@^1.4.0: +loose-envify@^1.0.0, loose-envify@^1.1.0, loose-envify@^1.4.0: version "1.4.0" resolved "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz" integrity sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q== @@ -13281,7 +13311,7 @@ promzard@^1.0.0: dependencies: read "^2.0.0" -prop-types@15.8.1, prop-types@^15.7.2, prop-types@^15.8.1: +prop-types@15.8.1, prop-types@^15.6.0, prop-types@^15.7.2, prop-types@^15.8.1: version "15.8.1" resolved "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz" integrity sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg== @@ -13440,6 +13470,17 @@ react-app-polyfill@^3.0.0: regenerator-runtime "^0.13.9" whatwg-fetch "^3.6.2" +react-calendar@^4.8.0: + version "4.8.0" + resolved "https://registry.yarnpkg.com/react-calendar/-/react-calendar-4.8.0.tgz#61edbba6d17e7ef8a8012de9143b5e5ff41104c8" + integrity sha512-qFgwo+p58sgv1QYMI1oGNaop90eJVKuHTZ3ZgBfrrpUb+9cAexxsKat0sAszgsizPMVo7vOXedV7Lqa0GQGMvA== + dependencies: + "@wojtekmaj/date-utils" "^1.1.3" + clsx "^2.0.0" + get-user-locale "^2.2.1" + prop-types "^15.6.0" + warning "^4.0.0" + react-code-blocks@^0.1.6: version "0.1.6" resolved "https://registry.yarnpkg.com/react-code-blocks/-/react-code-blocks-0.1.6.tgz#ec64e7899223d3e910eb916465a66d95ce1ae1b2" @@ -15880,6 +15921,13 @@ walker@^1.0.7: dependencies: makeerror "1.0.12" +warning@^4.0.0: + version "4.0.3" + resolved "https://registry.yarnpkg.com/warning/-/warning-4.0.3.tgz#16e9e077eb8a86d6af7d64aa1e05fd85b4678ca3" + integrity sha512-rpJyN222KWIvHJ/F53XSZv0Zl/accqHR8et1kpaMTD/fLCRxtV8iX8czMzY7sVZupTI3zcUTg8eycS2kNF9l6w== + dependencies: + loose-envify "^1.0.0" + watchpack@^2.4.0: version "2.4.0" resolved "https://registry.npmjs.org/watchpack/-/watchpack-2.4.0.tgz"