Skip to content

Instantly share code, notes, and snippets.

@nishimweprince
Created July 28, 2025 10:30
Show Gist options
  • Select an option

  • Save nishimweprince/91d938fc2743408dbbe24dda0b747654 to your computer and use it in GitHub Desktop.

Select an option

Save nishimweprince/91d938fc2743408dbbe24dda0b747654 to your computer and use it in GitHub Desktop.
Date and time picker styled by Shadcn UI.
import * as React from 'react';
import { ChevronDownIcon } from 'lucide-react';
import { Button } from '@/components/ui/button';
import { Calendar } from '@/components/ui/calendar';
import {
Popover,
PopoverContent,
PopoverTrigger,
} from '@/components/ui/popover';
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from '@/components/ui/select';
import moment from 'moment';
interface DateTimePickerProps {
label?: string;
required?: boolean;
placeholder?: string | React.ReactNode;
value?: Date | undefined;
onChange?: (date: Date) => void;
defaultValue?: Date | undefined;
}
export function DateTimePicker({
label,
required,
value,
placeholder,
onChange,
defaultValue,
}: DateTimePickerProps) {
const [open, setOpen] = React.useState(false);
const [date, setDate] = React.useState<Date | undefined>(value || defaultValue);
const [hour, setHour] = React.useState<string>(
value ? moment(value).format('HH') : defaultValue ? moment(defaultValue).format('HH') : '10'
);
const [minute, setMinute] = React.useState<string>(
value ? moment(value).format('mm') : defaultValue ? moment(defaultValue).format('mm') : '30'
);
// Update internal state when value prop changes
React.useEffect(() => {
if (value) {
setDate(value);
setHour(moment(value).format('HH'));
setMinute(moment(value).format('mm'));
}
}, [value]);
// Handle date selection
const handleDateSelect = (selectedDate: Date | undefined) => {
if (selectedDate) {
const newDate = moment(selectedDate)
.hour(parseInt(hour))
.minute(parseInt(minute))
.toDate();
setDate(newDate);
onChange?.(newDate);
}
};
// Handle hour change
const handleHourChange = (newHour: string) => {
setHour(newHour);
if (date) {
const newDate = moment(date)
.hour(parseInt(newHour))
.minute(parseInt(minute))
.toDate();
setDate(newDate);
onChange?.(newDate);
}
};
// Handle minute change
const handleMinuteChange = (newMinute: string) => {
setMinute(newMinute);
if (date) {
const newDate = moment(date)
.hour(parseInt(hour))
.minute(parseInt(newMinute))
.toDate();
setDate(newDate);
onChange?.(newDate);
}
};
// Generate hour options (00-23)
const hourOptions = Array.from({ length: 24 }, (_, i) =>
i.toString().padStart(2, '0')
);
// Generate minute options (00-59)
const minuteOptions = Array.from({ length: 60 }, (_, i) =>
i.toString().padStart(2, '0')
);
// Get display value
const getDisplayValue = () => {
if (date) {
return moment(date).format('DD/MM/YYYY HH:mm');
}
return placeholder || 'Select date & time';
};
return (
<section className="flex flex-col gap-3 w-full">
<fieldset className="flex flex-col gap-2 border-0 p-0 m-0">
<legend className="sr-only">{label || 'Date & Time'}</legend>
<label className="px-1 text-sm font-medium">
{label || 'Date & Time'}
{required && <span className="text-red-700 ml-1">*</span>}
</label>
<Popover open={open} onOpenChange={setOpen}>
<PopoverTrigger asChild>
<Button
variant="outline"
id="date-picker"
className="w-full justify-between font-normal h-10"
>
{getDisplayValue()}
<ChevronDownIcon className="h-4 w-4" />
</Button>
</PopoverTrigger>
<PopoverContent className="overflow-hidden p-0 w-auto" align="start">
<article className="flex flex-col">
<Calendar
mode="single"
selected={date}
className="w-full"
captionLayout="dropdown"
onSelect={handleDateSelect}
/>
{/* Time Picker */}
<section className="flex gap-2 p-3 border-t">
<fieldset className="flex-1">
<legend className="sr-only">Hour selection</legend>
<label className="text-xs text-muted-foreground mb-1 block">
Hour
</label>
<Select value={hour} onValueChange={handleHourChange}>
<SelectTrigger className="h-8">
<SelectValue placeholder="Hour" />
</SelectTrigger>
<SelectContent>
{hourOptions.map((h) => (
<SelectItem key={h} value={h}>
{h}
</SelectItem>
))}
</SelectContent>
</Select>
</fieldset>
<fieldset className="flex-1">
<legend className="sr-only">Minute selection</legend>
<label className="text-xs text-muted-foreground mb-1 block">
Minute
</label>
<Select value={minute} onValueChange={handleMinuteChange}>
<SelectTrigger className="h-8">
<SelectValue placeholder="Minute" />
</SelectTrigger>
<SelectContent>
{minuteOptions.map((m) => (
<SelectItem key={m} value={m}>
{m}
</SelectItem>
))}
</SelectContent>
</Select>
</fieldset>
</section>
</article>
</PopoverContent>
</Popover>
</fieldset>
</section>
);
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment