forked from cerc-io/snowballtools-base
Merge pull request #79 from snowball-tools/ayungavis/T-4842-datepicker
[T-4842: feat] Calendar component
This commit is contained in:
commit
abfe6a523c
6
.vscode/settings.json
vendored
Normal file
6
.vscode/settings.json
vendored
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
{
|
||||||
|
// IntelliSense for taiwind variants
|
||||||
|
"tailwindCSS.experimental.classRegex": [
|
||||||
|
["tv\\((([^()]*|\\([^()]*\\))*)\\)", "[\"'`]([^\"'`]*).*?[\"'`]"]
|
||||||
|
]
|
||||||
|
}
|
@ -13,13 +13,14 @@
|
|||||||
"@types/react": "^18.2.42",
|
"@types/react": "^18.2.42",
|
||||||
"@types/react-dom": "^18.2.17",
|
"@types/react-dom": "^18.2.17",
|
||||||
"assert": "^2.1.0",
|
"assert": "^2.1.0",
|
||||||
"date-fns": "^3.0.1",
|
"date-fns": "^3.3.1",
|
||||||
"downshift": "^8.2.3",
|
"downshift": "^8.2.3",
|
||||||
"eslint-config-react-app": "^7.0.1",
|
"eslint-config-react-app": "^7.0.1",
|
||||||
"gql-client": "^1.0.0",
|
"gql-client": "^1.0.0",
|
||||||
"luxon": "^3.4.4",
|
"luxon": "^3.4.4",
|
||||||
"octokit": "^3.1.2",
|
"octokit": "^3.1.2",
|
||||||
"react": "^18.2.0",
|
"react": "^18.2.0",
|
||||||
|
"react-calendar": "^4.8.0",
|
||||||
"react-code-blocks": "^0.1.6",
|
"react-code-blocks": "^0.1.6",
|
||||||
"react-day-picker": "^8.9.1",
|
"react-day-picker": "^8.9.1",
|
||||||
"react-dom": "^18.2.0",
|
"react-dom": "^18.2.0",
|
||||||
|
128
packages/frontend/src/components/shared/Calendar/Calendar.css
Normal file
128
packages/frontend/src/components/shared/Calendar/Calendar.css
Normal file
@ -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;
|
||||||
|
}
|
@ -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<typeof calendarTheme>;
|
298
packages/frontend/src/components/shared/Calendar/Calendar.tsx
Normal file
298
packages/frontend/src/components/shared/Calendar/Calendar.tsx
Normal file
@ -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<CalendarView>('month');
|
||||||
|
const [activeDate, setActiveDate] = useState<Date>(
|
||||||
|
activeStartDateProp ?? today,
|
||||||
|
);
|
||||||
|
const [value, setValue] = useState<Value>(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<HTMLButtonElement>) => {
|
||||||
|
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 (
|
||||||
|
<div
|
||||||
|
{...wrapperProps}
|
||||||
|
className={wrapper({ className: wrapperProps?.className })}
|
||||||
|
>
|
||||||
|
{/* Calendar wrapper */}
|
||||||
|
<div
|
||||||
|
{...calendarWrapperProps}
|
||||||
|
className={calendar({ className: calendarWrapperProps?.className })}
|
||||||
|
>
|
||||||
|
{/* Navigation */}
|
||||||
|
<div className={navigation()}>
|
||||||
|
<Button iconOnly size="sm" variant="ghost" onClick={handlePrevious}>
|
||||||
|
<ChevronLeft />
|
||||||
|
</Button>
|
||||||
|
<div className={actionsClass()}>
|
||||||
|
<Button
|
||||||
|
variant="unstyled"
|
||||||
|
className={button()}
|
||||||
|
rightIcon={
|
||||||
|
<ChevronGrabberHorizontal className="text-elements-low-em" />
|
||||||
|
}
|
||||||
|
onClick={() => handleChangeView('year')}
|
||||||
|
>
|
||||||
|
{month}
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
variant="unstyled"
|
||||||
|
className={button()}
|
||||||
|
rightIcon={
|
||||||
|
<ChevronGrabberHorizontal className="text-elements-low-em" />
|
||||||
|
}
|
||||||
|
onClick={() => handleChangeView('decade')}
|
||||||
|
>
|
||||||
|
{year}
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
<Button iconOnly size="sm" variant="ghost" onClick={handleNext}>
|
||||||
|
<ChevronRight />
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Calendar */}
|
||||||
|
<ReactCalendar
|
||||||
|
{...props}
|
||||||
|
activeStartDate={activeDate}
|
||||||
|
view={view}
|
||||||
|
value={value}
|
||||||
|
showNavigation={false}
|
||||||
|
selectRange={selectRange}
|
||||||
|
onChange={handleChange}
|
||||||
|
onClickMonth={(date) => handleChangeNavigation('month', date)}
|
||||||
|
onClickYear={(date) => handleChangeNavigation('year', date)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Footer or CTA */}
|
||||||
|
<div
|
||||||
|
{...footerProps}
|
||||||
|
className={footer({ className: footerProps?.className })}
|
||||||
|
>
|
||||||
|
{actions ? (
|
||||||
|
actions
|
||||||
|
) : (
|
||||||
|
<>
|
||||||
|
<Button variant="tertiary" onClick={onCancel}>
|
||||||
|
Cancel
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
disabled={!value}
|
||||||
|
onClick={() => (value ? onSelect?.(value) : null)}
|
||||||
|
>
|
||||||
|
Select
|
||||||
|
</Button>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
@ -0,0 +1 @@
|
|||||||
|
export * from './Calendar';
|
@ -0,0 +1,20 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { CustomIcon, CustomIconProps } from './CustomIcon';
|
||||||
|
|
||||||
|
export const ChevronGrabberHorizontal = (props: CustomIconProps) => {
|
||||||
|
return (
|
||||||
|
<CustomIcon
|
||||||
|
width="20"
|
||||||
|
height="20"
|
||||||
|
viewBox="0 0 20 20"
|
||||||
|
fill="none"
|
||||||
|
{...props}
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
d="M6.66666 12.5L9.99999 15.8333L13.3333 12.5M6.66666 7.5L9.99999 4.16666L13.3333 7.5"
|
||||||
|
stroke="currentColor"
|
||||||
|
strokeLinecap="square"
|
||||||
|
/>
|
||||||
|
</CustomIcon>
|
||||||
|
);
|
||||||
|
};
|
@ -0,0 +1,21 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { CustomIcon, CustomIconProps } from './CustomIcon';
|
||||||
|
|
||||||
|
export const ChevronLeft = (props: CustomIconProps) => {
|
||||||
|
return (
|
||||||
|
<CustomIcon
|
||||||
|
width="16"
|
||||||
|
height="16"
|
||||||
|
viewBox="0 0 16 16"
|
||||||
|
fill="none"
|
||||||
|
{...props}
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
fillRule="evenodd"
|
||||||
|
clipRule="evenodd"
|
||||||
|
d="M10.7071 2.66666L5.37378 8.00001L10.7071 13.3333L10 14.0404L3.95956 8.00001L10 1.95956L10.7071 2.66666Z"
|
||||||
|
fill="currentColor"
|
||||||
|
/>
|
||||||
|
</CustomIcon>
|
||||||
|
);
|
||||||
|
};
|
@ -0,0 +1,21 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { CustomIcon, CustomIconProps } from './CustomIcon';
|
||||||
|
|
||||||
|
export const ChevronRight = (props: CustomIconProps) => {
|
||||||
|
return (
|
||||||
|
<CustomIcon
|
||||||
|
width="16"
|
||||||
|
height="16"
|
||||||
|
viewBox="0 0 16 16"
|
||||||
|
fill="none"
|
||||||
|
{...props}
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
fillRule="evenodd"
|
||||||
|
clipRule="evenodd"
|
||||||
|
d="M6.00001 1.95956L12.0405 7.99998L6.00001 14.0404L5.29291 13.3333L10.6262 7.99999L5.29291 2.66666L6.00001 1.95956Z"
|
||||||
|
fill="currentColor"
|
||||||
|
/>
|
||||||
|
</CustomIcon>
|
||||||
|
);
|
||||||
|
};
|
@ -1,2 +1,5 @@
|
|||||||
export * from './PlusIcon';
|
export * from './PlusIcon';
|
||||||
export * from './CustomIcon';
|
export * from './CustomIcon';
|
||||||
|
export * from './ChevronGrabberHorizontal';
|
||||||
|
export * from './ChevronLeft';
|
||||||
|
export * from './ChevronRight';
|
||||||
|
@ -1,8 +1,13 @@
|
|||||||
import { Button, ButtonOrLinkProps } from 'components/shared/Button';
|
import { Button, ButtonOrLinkProps } from 'components/shared/Button';
|
||||||
|
import { Calendar } from 'components/shared/Calendar';
|
||||||
import { PlusIcon } from 'components/shared/CustomIcon';
|
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 Page = () => {
|
||||||
|
const [singleDate, setSingleDate] = useState<Value>();
|
||||||
|
const [dateRange, setDateRange] = useState<Value>();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="relative h-full min-h-full">
|
<div className="relative h-full min-h-full">
|
||||||
<div className="flex flex-col items-center justify-center max-w-7xl mx-auto px-20 py-20">
|
<div className="flex flex-col items-center justify-center max-w-7xl mx-auto px-20 py-20">
|
||||||
@ -13,6 +18,7 @@ const Page = () => {
|
|||||||
packages/frontend/src/pages/components/index.tsx
|
packages/frontend/src/pages/components/index.tsx
|
||||||
</code>
|
</code>
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<div className="w-full h border border-gray-200 px-20 my-10" />
|
<div className="w-full h border border-gray-200 px-20 my-10" />
|
||||||
|
|
||||||
{/* Insert Components here */}
|
{/* Insert Components here */}
|
||||||
@ -69,6 +75,31 @@ const Page = () => {
|
|||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div className="w-full h border border-gray-200 px-20 my-10" />
|
||||||
|
<div className="flex flex-col gap-10 items-center justify-between">
|
||||||
|
<h1 className="text-2xl font-bold">Calendar</h1>
|
||||||
|
<div className="flex flex-col gap-10">
|
||||||
|
<div className="space-y-5 flex flex-col items-center">
|
||||||
|
<p>Selected date: {singleDate?.toString()}</p>
|
||||||
|
<Calendar
|
||||||
|
value={singleDate}
|
||||||
|
onChange={setSingleDate}
|
||||||
|
onSelect={setSingleDate}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="space-y-5 flex flex-col items-center">
|
||||||
|
<p>
|
||||||
|
Start date:{' '}
|
||||||
|
{dateRange instanceof Array ? dateRange[0]?.toString() : ''}{' '}
|
||||||
|
<br />
|
||||||
|
End date:{' '}
|
||||||
|
{dateRange instanceof Array ? dateRange[1]?.toString() : ''}
|
||||||
|
</p>
|
||||||
|
<Calendar selectRange value={dateRange} onChange={setDateRange} />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
@ -144,6 +144,9 @@ export default withMT({
|
|||||||
boxShadow: {
|
boxShadow: {
|
||||||
button:
|
button:
|
||||||
'inset 0px 0px 4px rgba(255, 255, 255, 0.25), inset 0px -2px 0px rgba(0, 0, 0, 0.04)',
|
'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: {
|
spacing: {
|
||||||
2.5: '0.625rem',
|
2.5: '0.625rem',
|
||||||
|
62
yarn.lock
62
yarn.lock
@ -3910,6 +3910,18 @@
|
|||||||
dependencies:
|
dependencies:
|
||||||
"@types/node" "*"
|
"@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":
|
"@types/long@^4.0.0", "@types/long@^4.0.1":
|
||||||
version "4.0.2"
|
version "4.0.2"
|
||||||
resolved "https://registry.yarnpkg.com/@types/long/-/long-4.0.2.tgz#b74129719fc8d11c01868010082d483b7545591a"
|
resolved "https://registry.yarnpkg.com/@types/long/-/long-4.0.2.tgz#b74129719fc8d11c01868010082d483b7545591a"
|
||||||
@ -4687,6 +4699,11 @@
|
|||||||
"@webassemblyjs/ast" "1.11.6"
|
"@webassemblyjs/ast" "1.11.6"
|
||||||
"@xtuc/long" "4.2.2"
|
"@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":
|
"@wry/caches@^1.0.0":
|
||||||
version "1.0.1"
|
version "1.0.1"
|
||||||
resolved "https://registry.yarnpkg.com/@wry/caches/-/caches-1.0.1.tgz#8641fd3b6e09230b86ce8b93558d44cf1ece7e52"
|
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"
|
resolved "https://registry.yarnpkg.com/clone/-/clone-1.0.4.tgz#da309cc263df15994c688ca902179ca3c7cd7c7e"
|
||||||
integrity sha512-JQHZ2QMW6l3aH/j6xCqQThY/9OH4D/9ls34cgkUBiEeocRTU04tHfKPBsUK1PqZCUQM7GiA0IIXJSuXHI64Kbg==
|
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:
|
cmd-shim@6.0.1:
|
||||||
version "6.0.1"
|
version "6.0.1"
|
||||||
resolved "https://registry.yarnpkg.com/cmd-shim/-/cmd-shim-6.0.1.tgz#a65878080548e1dca760b3aea1e21ed05194da9d"
|
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-mimetype "^2.3.0"
|
||||||
whatwg-url "^8.0.0"
|
whatwg-url "^8.0.0"
|
||||||
|
|
||||||
date-fns@^3.0.1:
|
date-fns@^3.3.1:
|
||||||
version "3.0.1"
|
version "3.3.1"
|
||||||
resolved "https://registry.yarnpkg.com/date-fns/-/date-fns-3.0.1.tgz#a95b3e8296e72cf57c99819f37679aa27ca65ec4"
|
resolved "https://registry.yarnpkg.com/date-fns/-/date-fns-3.3.1.tgz#7581daca0892d139736697717a168afbb908cfed"
|
||||||
integrity sha512-cr9igCUa0QSqgAMj7JOrYTY6Nh1rmyGrFDko7ADqfmaQqP/I2N4rlfrLl7AWuzDaoIpz6MNjoEcTPzgZYIrhnA==
|
integrity sha512-y8e109LYGgoQDveiEBD3DYXKba1jWf5BA8YU1FL5Tvm0BTdEfy54WLCwnuYWZNnzzvALy/QQ4Hov+Q9RVRv+Zw==
|
||||||
|
|
||||||
dateformat@^3.0.3:
|
dateformat@^3.0.3:
|
||||||
version "3.0.3"
|
version "3.0.3"
|
||||||
@ -8720,6 +8742,14 @@ get-tsconfig@^4.7.0:
|
|||||||
dependencies:
|
dependencies:
|
||||||
resolve-pkg-maps "^1.0.0"
|
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:
|
git-raw-commits@^3.0.0:
|
||||||
version "3.0.0"
|
version "3.0.0"
|
||||||
resolved "https://registry.yarnpkg.com/git-raw-commits/-/git-raw-commits-3.0.0.tgz#5432f053a9744f67e8db03dbc48add81252cfdeb"
|
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"
|
resolved "https://registry.yarnpkg.com/lodash.isstring/-/lodash.isstring-4.0.1.tgz#d527dfb5456eca7cc9bb95d5daeaf88ba54a5451"
|
||||||
integrity sha512-0wJxfxH1wgO3GrbuP+dTTk7op+6L41QCXbGINEmD+ny/G/eCqGzxyCsh7159S+mgDDcoarnBw6PC1PS5+wUGgw==
|
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"
|
version "4.1.2"
|
||||||
resolved "https://registry.npmjs.org/lodash.memoize/-/lodash.memoize-4.1.2.tgz"
|
resolved "https://registry.npmjs.org/lodash.memoize/-/lodash.memoize-4.1.2.tgz"
|
||||||
integrity sha512-t7j+NzmgnQzTAYXcsHYLgimltOV1MXHtlOWf6GjL9Kj8GK5FInw5JotxvbOs+IvV1/Dzo04/fCGfLVs7aXb4Ag==
|
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"
|
resolved "https://registry.yarnpkg.com/long/-/long-5.2.3.tgz#a3ba97f3877cf1d778eccbcb048525ebb77499e1"
|
||||||
integrity sha512-lcHwpNoggQTObv5apGNCTdJrO69eHOZMi4BNC+rTLER8iHAqGrUVeLh/irVIM7zTw2bOXA8T6uNPeujwOLg/2Q==
|
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"
|
version "1.4.0"
|
||||||
resolved "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz"
|
resolved "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz"
|
||||||
integrity sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==
|
integrity sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==
|
||||||
@ -13281,7 +13311,7 @@ promzard@^1.0.0:
|
|||||||
dependencies:
|
dependencies:
|
||||||
read "^2.0.0"
|
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"
|
version "15.8.1"
|
||||||
resolved "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz"
|
resolved "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz"
|
||||||
integrity sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==
|
integrity sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==
|
||||||
@ -13440,6 +13470,17 @@ react-app-polyfill@^3.0.0:
|
|||||||
regenerator-runtime "^0.13.9"
|
regenerator-runtime "^0.13.9"
|
||||||
whatwg-fetch "^3.6.2"
|
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:
|
react-code-blocks@^0.1.6:
|
||||||
version "0.1.6"
|
version "0.1.6"
|
||||||
resolved "https://registry.yarnpkg.com/react-code-blocks/-/react-code-blocks-0.1.6.tgz#ec64e7899223d3e910eb916465a66d95ce1ae1b2"
|
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:
|
dependencies:
|
||||||
makeerror "1.0.12"
|
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:
|
watchpack@^2.4.0:
|
||||||
version "2.4.0"
|
version "2.4.0"
|
||||||
resolved "https://registry.npmjs.org/watchpack/-/watchpack-2.4.0.tgz"
|
resolved "https://registry.npmjs.org/watchpack/-/watchpack-2.4.0.tgz"
|
||||||
|
Loading…
Reference in New Issue
Block a user