Created
December 7, 2021 22:00
-
-
Save aaronmcadam/81aa46e96d93c3a57829f4a6c28dd18b to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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