Skip to content

Instantly share code, notes, and snippets.

@aaronmcadam
Created December 7, 2021 22:00
Show Gist options
  • Save aaronmcadam/81aa46e96d93c3a57829f4a6c28dd18b to your computer and use it in GitHub Desktop.
Save aaronmcadam/81aa46e96d93c3a57829f4a6c28dd18b to your computer and use it in GitHub Desktop.
import {
Box,
Button,
ButtonGroup,
CalendarIconSolid,
ChevronLeftIconSolid,
ChevronRightIconSolid,
Divider,
Heading,
HStack,
Input,
InputGroup,
InputRightElement,
Popover,
PopoverBody,
PopoverContent,
PopoverTrigger,
SimpleGrid,
Stack,
Text,
useMode,
useOutsideClick,
} from '@backstage/spotlight';
import * as dateFns from 'date-fns';
import {
Calendar,
DateObj,
GetBackForwardPropsOptions,
RenderProps,
useDayzed,
} from 'dayzed';
import * as React from 'react';
const MONTH_NAMES = [
'January',
'February',
'March',
'April',
'May',
'June',
'July',
'August',
'September',
'October',
'November',
'December',
];
const DAY_NAMES = ['Mo', 'Tue', 'We', 'Th', 'Fr', 'Sa', 'Su'];
const DATE_FORMAT = 'MMMM d, yyyy';
interface DatePickerButtonsProps {
calendars: Calendar[];
getBackProps: (data: GetBackForwardPropsOptions) => Record<string, any>;
getForwardProps: (data: GetBackForwardPropsOptions) => Record<string, any>;
}
const DatePickerButtons = (props: DatePickerButtonsProps) => {
const { calendars, getBackProps, getForwardProps } = props;
return (
<ButtonGroup isAttached={true}>
<Button {...getBackProps({ calendars })} size="xs" mr="-px">
<ChevronLeftIconSolid color="gray.500" />
</Button>
<Button {...getForwardProps({ calendars })} size="xs">
<ChevronRightIconSolid color="gray.500" />
</Button>
</ButtonGroup>
);
};
const DatePickerCalendar = (props: RenderProps) => {
const { calendars, getDateProps, getBackProps, getForwardProps } = props;
const mode = useMode();
if (!calendars.length) {
return null;
}
return (
<Box>
<HStack spacing={6} alignItems="baseline">
{calendars.map((calendar) => {
return (
<Stack key={`${calendar.month}${calendar.year}`}>
<HStack justify="space-between">
<Heading fontSize="sm" fontWeight="medium">
{MONTH_NAMES[calendar.month]} {calendar.year}
</Heading>
<DatePickerButtons
calendars={calendars}
getBackProps={getBackProps}
getForwardProps={getForwardProps}
/>
</HStack>
<Divider />
<SimpleGrid columns={7} spacing={0} textAlign="center">
{DAY_NAMES.map((day) => {
return (
<Box key={`${calendar.month}${calendar.year}${day}`}>
<Text fontSize="xs" fontWeight="medium" color="gray.500">
{day}
</Text>
</Box>
);
})}
{calendar.weeks.map((week, weekIndex) => {
return (week as DateObj[]).map((dateObj: DateObj, index) => {
const {
date,
today,
prevMonth,
nextMonth,
selected,
selectable,
} = dateObj;
const key = `${calendar.month}${calendar.year}${weekIndex}${index}`;
const isDisabled = prevMonth || nextMonth || !selectable;
const style = () => {
const obj: Record<string, string> = {
variant: 'secondary',
};
// Make sure when today is selected that it has the primary background colour.
if (!selected && today) {
obj.bg = mode('gray.100', 'gray.700');
}
if (selected) {
obj.variant = 'primary';
}
return obj;
};
return (
<Button
{...getDateProps({
dateObj,
disabled: isDisabled,
})}
key={key}
rounded="full"
size="sm"
boxSize={8}
borderWidth={0}
boxShadow="none"
fontWeight="normal"
{...style()}
>
{date.getDate()}
</Button>
);
});
})}
</SimpleGrid>
</Stack>
);
})}
</HStack>
</Box>
);
};
export interface DatePickerProps {
value?: Date;
onChange: (date?: Date) => void;
}
export const DatePicker = (props: DatePickerProps) => {
const { value, onChange } = props;
const mode = useMode();
const ref = React.useRef<HTMLElement>(null);
const initialFocusRef = React.useRef<HTMLInputElement>(null);
const [proposedDate, setProposedDate] = React.useState<string>(
value ? dateFns.format(value, DATE_FORMAT) : ''
);
const [popoverOpen, setPopoverOpen] = React.useState(false);
useOutsideClick({
ref: ref,
handler: () => setPopoverOpen(false),
});
const onChangePrime = (date: Date) => {
onChange(date);
if (date) {
setProposedDate(dateFns.format(date, DATE_FORMAT));
}
};
const onDateSelected = (options: { selectable: boolean; date: Date }) => {
const { selectable, date } = options;
if (!selectable) {
return;
}
if (date) {
onChangePrime(date);
setPopoverOpen(false);
return;
}
};
const dayzedData = useDayzed({
onDateSelected,
selected: value,
showOutsideDays: true,
// Disable days in the past.
minDate: dateFns.startOfToday(),
// Set the first day of the week to Monday.
firstDayOfWeek: 1,
});
return (
<Popover
placement="bottom-start"
variant="responsive"
isOpen={popoverOpen}
onClose={() => setPopoverOpen(false)}
initialFocusRef={initialFocusRef}
isLazy={true}
>
<PopoverTrigger>
{/* We want to make the whole input group clickable. */}
<InputGroup
onClick={() => setPopoverOpen(!popoverOpen)}
cursor="pointer"
>
<Input
// Stops users trying to edit the date inside the input.
isReadOnly={true}
placeholder="Choose a date"
cursor="pointer"
value={proposedDate}
ref={initialFocusRef}
onChange={(e) => {
setProposedDate(e.target.value);
}}
/>
<InputRightElement>
<CalendarIconSolid color="gray.400" />
</InputRightElement>
</InputGroup>
</PopoverTrigger>
<PopoverContent ref={ref} w="auto" bg={mode('white', 'gray.800')}>
<PopoverBody p={4}>
<DatePickerCalendar {...dayzedData} />
</PopoverBody>
</PopoverContent>
</Popover>
);
};
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment