Skip to main content
Glama
date-month-picker.tsx8.06 kB
'use client' import * as React from 'react' import { ChevronLeft, ChevronRight } from 'lucide-react' import { buttonVariants } from './button' import { cn } from '@/lib/utils' import { format } from 'date-fns' import { zhCN, enUS } from 'date-fns/locale' import { useLanguage } from '@/contexts/language-context' import { Button } from './button' import { Popover, PopoverContent, PopoverTrigger } from './popover' import { Calendar1 as CalenderMonthIcon } from 'lucide-react' type Month = { number: number name: string } function generateMonths(language?: string) { const locale = language === 'zh-CN' ? zhCN : enUS const months: Month[][] = [[], [], []] for (let i = 0; i < 12; i++) { months[Math.floor(i / 4)].push({ number: i, name: format(new Date(0, i), 'MMM', { locale }), }) } return months } type MonthCalProps = { selectedMonth?: Date onMonthSelect?: (date: Date) => void onYearForward?: () => void onYearBackward?: () => void callbacks?: { yearLabel?: (year: number) => string monthLabel?: (month: Month) => string } variant?: { calendar?: { main?: ButtonVariant selected?: ButtonVariant } chevrons?: ButtonVariant } minDate?: Date maxDate?: Date disabledDates?: Date[] } type ButtonVariant = | 'default' | 'outline' | 'ghost' | 'link' | 'destructive' | 'secondary' | null | undefined function MonthPicker({ onMonthSelect, selectedMonth, minDate, maxDate, disabledDates, callbacks, onYearBackward, onYearForward, variant, className, ...props }: React.HTMLAttributes<HTMLDivElement> & MonthCalProps) { return ( <div className={cn('min-w-[200px] w-[280px] p-3', className)} {...props}> <div className='flex flex-col sm:flex-row space-y-4 sm:space-x-4 sm:space-y-0'> <div className='space-y-4 w-full'> <MonthCal onMonthSelect={onMonthSelect} callbacks={callbacks} selectedMonth={selectedMonth} onYearBackward={onYearBackward} onYearForward={onYearForward} variant={variant} minDate={minDate} maxDate={maxDate} disabledDates={disabledDates}></MonthCal> </div> </div> </div> ) } function MonthCal({ selectedMonth, onMonthSelect, callbacks, variant, minDate, maxDate, disabledDates, onYearBackward, onYearForward, }: MonthCalProps) { const [year, setYear] = React.useState<number>( selectedMonth?.getFullYear() ?? new Date().getFullYear() ) const [month, setMonth] = React.useState<number>( selectedMonth?.getMonth() ?? new Date().getMonth() ) const [menuYear, setMenuYear] = React.useState<number>(year) if (minDate && maxDate && minDate > maxDate) minDate = maxDate const disabledDatesMapped = disabledDates?.map((d) => { return { year: d.getFullYear(), month: d.getMonth() } }) const { language } = useLanguage() return ( <> <div className='flex justify-center pt-1 relative items-center'> <div className='text-sm font-medium'> {callbacks?.yearLabel ? callbacks?.yearLabel(menuYear) : menuYear} </div> <div className='space-x-1 flex items-center'> <button onClick={() => { setMenuYear(menuYear - 1) if (onYearBackward) onYearBackward() }} className={cn( buttonVariants({ variant: variant?.chevrons ?? 'outline' }), 'inline-flex items-center justify-center h-7 w-7 p-0 absolute left-1' )}> <ChevronLeft className='opacity-50 h-4 w-4' /> </button> <button onClick={() => { setMenuYear(menuYear + 1) if (onYearForward) onYearForward() }} className={cn( buttonVariants({ variant: variant?.chevrons ?? 'outline' }), 'inline-flex items-center justify-center h-7 w-7 p-0 absolute right-1' )}> <ChevronRight className='opacity-50 h-4 w-4' /> </button> </div> </div> <table className='w-full border-collapse space-y-1'> <tbody> {generateMonths(language).map((monthRow, a) => { return ( <tr key={'row-' + a} className='flex w-full mt-2'> {monthRow.map((m) => { return ( <td key={m.number} className='h-10 w-1/4 text-center text-sm p-0 relative [&:has([aria-selected].day-range-end)]:rounded-r-md [&:has([aria-selected].day-outside)]:bg-accent/50 [&:has([aria-selected])]:bg-accent first:[&:has([aria-selected])]:rounded-l-md last:[&:has([aria-selected])]:rounded-r-md focus-within:relative focus-within:z-20'> <button onClick={() => { setMonth(m.number) setYear(menuYear) if (onMonthSelect) onMonthSelect(new Date(menuYear, m.number)) }} disabled={ (maxDate ? menuYear > maxDate?.getFullYear() || (menuYear == maxDate?.getFullYear() && m.number > maxDate.getMonth()) : false) || (minDate ? menuYear < minDate?.getFullYear() || (menuYear == minDate?.getFullYear() && m.number < minDate.getMonth()) : false) || (disabledDatesMapped ? disabledDatesMapped?.some( (d) => d.year == menuYear && d.month == m.number ) : false) } className={cn( buttonVariants({ variant: month == m.number && menuYear == year ? variant?.calendar?.selected ?? 'default' : variant?.calendar?.main ?? 'ghost', }), 'h-full w-full p-0 font-normal aria-selected:opacity-100' )}> {callbacks?.monthLabel ? callbacks.monthLabel(m) : m.name} </button> </td> ) })} </tr> ) })} </tbody> </table> </> ) } export type MonthData = { year: number month: number } export function DateMonthPicker({ value, onChange, }: { value?: MonthData onChange?: (monthData: MonthData | undefined) => void }) { const [selectedMonth, setSelectedMonth] = React.useState<Date>() React.useEffect(() => { if (!value) { setSelectedMonth(undefined) return } setSelectedMonth(new Date(value.year, value.month)) }, [value]) function handleSelected(date: Date) { setSelectedMonth(date) onChange?.({ year: date.getFullYear(), month: date.getMonth(), }) } const { t, language } = useLanguage() return ( <Popover> <PopoverTrigger asChild> <Button variant={'outline'} className={cn( 'justify-start text-left font-normal', !selectedMonth && 'text-muted-foreground' )}> <CalenderMonthIcon className='mr-2 h-4 w-4' /> {selectedMonth ? ( format(selectedMonth, language === 'zh-CN' ? 'yyyy年M月' : 'MMM yyyy') ) : ( <span>{t('common.pickMonth')}</span> )} </Button> </PopoverTrigger> <PopoverContent className='w-auto p-0'> <MonthPicker onMonthSelect={handleSelected} selectedMonth={selectedMonth} maxDate={new Date()} /> </PopoverContent> </Popover> ) }

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/itcook/graphiti-mcp-pro'

If you have feedback or need assistance with the MCP directory API, please join our Discord server