Skip to content

Instantly share code, notes, and snippets.

@tkorakas
Created July 4, 2022 20:43
Show Gist options
  • Save tkorakas/051463bf6a077f950f2510a1448e5ca9 to your computer and use it in GitHub Desktop.
Save tkorakas/051463bf6a077f950f2510a1448e5ca9 to your computer and use it in GitHub Desktop.
React component to select year/month using ChakraUI
import {
Input,
GridItem,
SimpleGrid,
Box,
Button,
Stack,
} from "@chakra-ui/react";
import { useReducer, useRef } from "react";
enum Selector {
Month = "month",
Year = "year",
}
enum Month {
Jan = 1,
Feb = 2,
Apr = 3,
Mar = 4,
May = 5,
Jun = 6,
Jul = 7,
Aug = 8,
Sep = 9,
Oct = 10,
Nov = 11,
Dec = 12,
}
const months = [
Month.Jan,
Month.Feb,
Month.Mar,
Month.Apr,
Month.May,
Month.Jun,
Month.Jul,
Month.Aug,
Month.Sep,
Month.Oct,
Month.Nov,
Month.Dec,
] as const;
type CalendarState = {
year: number;
month: Month;
selecting: Selector;
isOpen: boolean;
};
type Action =
| { type: "toggleSelection" }
| { type: "selectMonth"; month: Month }
| { type: "selectYear"; year: number }
| { type: "toggleCalendar" };
function reducer(state: CalendarState, action: Action): CalendarState {
if (action.type === "toggleSelection") {
return {
...state,
selecting:
state.selecting === Selector.Month ? Selector.Year : Selector.Month,
};
}
if (action.type === "selectMonth") {
return {
...state,
month: action.month,
};
}
if (action.type === "selectYear") {
return {
...state,
year: action.year,
selecting: Selector.Month,
};
}
if (action.type === "toggleCalendar") {
return {
...state,
isOpen: !state.isOpen,
};
}
return state;
}
import { useCallback, useEffect } from "react";
function useClickOutside(
ref: React.RefObject<HTMLElement>,
callback: () => void
) {
const handleClick = useCallback(
(event) => {
if (ref.current && !ref.current.contains(event.target)) {
callback();
}
},
[callback, ref]
);
useEffect(() => {
document.addEventListener("click", handleClick);
return () => {
document.removeEventListener("click", handleClick);
};
}, [handleClick]);
}
function useCalendar() {
const [state, dispatch] = useReducer(reducer, {
month: Month.Jan,
year: 2022,
selecting: Selector.Year,
isOpen: false,
});
const toggleSelection = () => {
dispatch({ type: "toggleSelection" });
};
const selectMonth = (month: Month) => () => {
dispatch({ type: "selectMonth", month });
};
const selectYear = (year: number) => () => {
dispatch({ type: "selectYear", year });
};
const toggleCalendar = () => {
dispatch({ type: "toggleCalendar" });
};
return {
state,
selectMonth,
selectYear,
toggleSelection,
toggleCalendar,
};
}
type MonthSelectorProps = {
selectMonth: (month: Month) => () => void;
selectedMonth: Month;
};
function MonthSelector({ selectMonth, selectedMonth }: MonthSelectorProps) {
return (
<>
{months.map((month) => (
<Button
onClick={selectMonth(month)}
key={month}
variant={selectedMonth === month ? "solid" : "ghost"}
size="md"
>
{Month[month]}
</Button>
))}
</>
);
}
function YearSelector() {
return <div>year selector</div>;
}
export function Calendar() {
const { state, toggleSelection, selectMonth, selectYear, toggleCalendar } =
useCalendar();
const calendarRef = useRef<HTMLDivElement>(null);
useClickOutside(calendarRef, toggleCalendar);
const isYearSelector = state.selecting === Selector.Year;
return (
<Box ref={calendarRef} maxW={300} py="20">
<Input
value={`${Month[state.month]} ${state.year}`}
onClick={toggleCalendar}
placeholder="Start date"
/>
{state.isOpen && (
<>
<Stack direction="row" mt="5" justifyContent="space-between">
{isYearSelector && <Button>&lt;</Button>}
<Button onClick={toggleSelection} isFullWidth variant="outline">
{Month[state.month]} {state.year}
</Button>
{isYearSelector && <Button>&gt;</Button>}
</Stack>
<SimpleGrid columns={3} gap="2" rows={3} pt="5">
{isYearSelector ? (
<YearSelector />
) : (
<MonthSelector
selectMonth={selectMonth}
selectedMonth={state.month}
/>
)}
</SimpleGrid>
</>
)}
</Box>
);
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment