Time Picker
Time Picker é um componente para React e Tailwind CSS, da biblioteca UI Layouts, com licença MIT. Copia e cola no teu projeto.
UI Layouts
MIT
other
O que é
Time Picker é um componente para React e Tailwind CSS, da biblioteca UI Layouts, com licença MIT. Copia e cola no teu projeto.
Como usar
Abre o componente no catálogo, carrega em copiar, e cola no teu projeto. O prompt copiado leva o código e as regras para o teu agente o reproduzir sem alterar nada.
Cinco cópias grátis por mês. Sem cartão.
Precisa de
npm i chrono-node@latest react-day-picker@^8.9.1
Licença
Este componente vem de UI Layouts e é redistribuído sob a licença MIT, que permite uso comercial. O aviso de copyright viaja com o código.
Time Picker
'use client';
import { SmartDatetimeInput } from '@/components/ui/datetime-input';
import React, { useState } from 'react';
export default function Component() {
const [selectedDate, setSelectedDate] = useState<Date | undefined>(undefined);
const handleDateChange = (date: Date) => {
setSelectedDate(date);
console.log('Selected date:', date);
};
return (
<div className='p-4 max-w-md mx-auto py-10'>
<SmartDatetimeInput
value={selectedDate}
showCalendar={false}
showTimePicker={true}
onValueChange={handleDateChange}
placeholder='Enter a date and time'
className='lg:w-96'
/>
{/* {selectedDate && (
<p className='mt-4'>Selected Date: {selectedDate.toLocaleString()}</p>
)} */}
</div>
);
}
// @ts-nocheck
'use client';
import { Button, buttonVariants } from '@/components/website/ui/button';
import { Calendar, type CalendarProps } from '@/components/website/ui/calendar';
import { Popover, PopoverContent, PopoverTrigger } from '@/components/website/ui/popover';
import { ScrollArea } from '@/components/website/ui/scroll-area';
import { cn } from '@/lib/utils';
import { parseDate } from 'chrono-node';
import { Calendar as CalendarIcon, LucideTextCursorInput } from 'lucide-react';
import React from 'react';
import type { ActiveModifiers } from 'react-day-picker';
/* -------------------------------------------------------------------------- */
/* Inspired By: */
/* @steventey */
/* ------------------https://dub.co/blog/smart-datetime-picker--------------- */
/* -------------------------------------------------------------------------- */
/**
* Utility function that parses dates.
* Parses a given date string using the `chrono-node` library.
*
* @param str - A string representation of a date and time.
* @returns A `Date` object representing the parsed date and time, or `null` if the string could not be parsed.
*/
export const parseDateTime = (str: Date | string) => {
if (str instanceof Date) return str;
return parseDate(str);
};
/**
* Converts a given timestamp or the current date and time to a string representation in the local time zone.
* format: `HH:mm`, adjusted for the local time zone.
*
* @param timestamp {Date | string}
* @returns A string representation of the timestamp
*/
export const getDateTimeLocal = (timestamp?: Date): string => {
const d = timestamp ? new Date(timestamp) : new Date();
if (d.toString() === 'Invalid Date') return '';
return new Date(d.getTime() - d.getTimezoneOffset() * 60000)
.toISOString()
.split(':')
.slice(0, 2)
.join(':');
};
/**
* Formats a given date and time object or string into a human-readable string representation.
* "MMM D, YYYY h:mm A" (e.g. "Jan 1, 2023 12:00 PM").
*
* @param datetime - {Date | string}
* @returns A string representation of the date and time
*/
const formatTimeOnly = (datetime: Date | string) => {
return new Date(datetime).toLocaleTimeString('en-US', {
hour: 'numeric',
minute: 'numeric',
hour12: true,
});
};
const formatDateOnly = (datetime: Date | string) => {
return new Date(datetime).toLocaleDateString('en-US', {
month: 'short',
day: 'numeric',
year: 'numeric',
});
};
const formatDateTime = (
datetime: Date | string,
showCalendar: boolean,
showTimePicker: boolean
) => {
if (!showCalendar && showTimePicker) {
return formatTimeOnly(datetime);
}
if (showCalendar && !showTimePicker) {
return formatDateOnly(datetime);
}
return new Date(datetime).toLocaleTimeString('en-US', {
month: 'short',
day: 'numeric',
year: 'numeric',
hour: 'numeric',
minute: 'numeric',
hour12: true,
});
};
const inputBase =
'bg-transparent focus:outline-hidden focus:ring-0 focus-within:outline-hidden focus-within:ring-0 sm:text-sm disabled:cursor-not-allowed disabled:opacity-50';
// @source: https://www.perplexity.ai/search/in-javascript-how-RfI7fMtITxKr5c.V9Lv5KA#1
// use this pattern to validate the transformed date string for the natural language input
const naturalInputValidationPattern = '^[A-Z][a-z]{2}sd{1,2},sd{4},sd{1,2}:d{2}s[AP]M$';
const DEFAULT_SIZE = 96;
/**
* Smart time input Docs: {@link: https://shadcn-extension.vercel.app/docs/smart-time-input}
*/
interface SmartDatetimeInputProps {
value?: Date;
onValueChange: (date: Date) => void;
showCalendar?: boolean;
showTimePicker?: boolean;
}
interface SmartDatetimeInputContextProps extends SmartDatetimeInputProps {
Time: string;
onTimeChange: (time: string) => void;
}
const SmartDatetimeInputContext = React.createContext<SmartDatetimeInputContextProps | null>(null);
const useSmartDateInput = () => {
const context = React.useContext(SmartDatetimeInputContext);
if (!context) {
throw new Error('useSmartDateInput must be used within SmartDateInputProvider');
}
return context;
};
export const SmartDatetimeInput = React.forwardRef<
HTMLInputElement,
Omit<
React.InputHTMLAttributes<HTMLInputElement>,
'type' | 'ref' | 'value' | 'defaultValue' | 'onBlur'
> &
SmartDatetimeInputProps
>(
(
{
className,
value,
onValueChange,
placeholder,
disabled,
showCalendar = true,
showTimePicker = true,
},
ref
) => {
const [Time, setTime] = React.useState<string>('');
const onTimeChange = React.useCallback((time: string) => {
setTime(time);
}, []);
// If neither calendar nor timepicker is specified, show both
const shouldShowBoth = showCalendar === showTimePicker;
return (
<SmartDatetimeInputContext.Provider
value={{
value,
onValueChange,
Time,
onTimeChange,
showCalendar: shouldShowBoth ? true : showCalendar,
showTimePicker: shouldShowBoth ? true : showTimePicker,
}}
>
<div className='flex items-center justify-center dark:bg-neutral-950 bg-neutral-50'>
<div
className={cn(
'flex gap-1 w-full p-1 items-center justify-between rounded-md border-2 transition-all',
'focus-within:outline-0 focus:outline-0 focus:ring-0',
'placeholder:text-muted-foreground focus-visible:outline-0 ',
className
)}
>
<DateTimeLocalInput />
<NaturalLanguageInput placeholder={placeholder} disabled={disabled} ref={ref} />
</div>
</div>
</SmartDatetimeInputContext.Provider>
);
}
);
SmartDatetimeInput.displayName = 'DatetimeInput';
// Make it a standalone component
const TimePicker = () => {
const { value, onValueChange, Time, onTimeChange } = useSmartDateInput();
const [activeIndex, setActiveIndex] = React.useState(-1);
const timestamp = 15;
const formateSelectedTime = React.useCallback(
(time: string, hour: number, partStamp: number) => {
onTimeChange(time);
const newVal = value ? new Date(value) : new Date();
// If no value exists, use current date but only set the time
newVal.setHours(hour, partStamp === 0 ? Number.parseInt('00') : timestamp * partStamp);
onValueChange(newVal);
},
[value, onValueChange, onTimeChange]
);
const handleKeydown = React.useCallback(
(e: React.KeyboardEvent<HTMLDivElement>) => {
e.stopPropagation();
if (!document) return;
const moveNext = () => {
const nextIndex = activeIndex + 1 > DEFAULT_SIZE - 1 ? 0 : activeIndex + 1;
const currentElm = document.getElementById(`time-${nextIndex}`);
currentElm?.focus();
setActiveIndex(nextIndex);
};
const movePrev = () => {
const prevIndex = activeIndex - 1 < 0 ? DEFAULT_SIZE - 1 : activeIndex - 1;
const currentElm = document.getElementById(`time-${prevIndex}`);
currentElm?.focus();
setActiveIndex(prevIndex);
};
const setElement = () => {
const currentElm = document.getElementById(`time-${activeIndex}`);
if (!currentElm) return;
currentElm.focus();
const timeValue = currentElm.textContent ?? '';
// this should work now haha that hour is what does the trick
const PM_AM = timeValue.split(' ')[1];
const PM_AM_hour = Number.parseInt(timeValue.split(' ')[0].split(':')[0]);
const hour =
PM_AM === 'AM'
? PM_AM_hour === 12
? 0
: PM_AM_hour
: PM_AM_hour === 12
? 12
: PM_AM_hour + 12;
const part = Math.floor(Number.parseInt(timeValue.split(' ')[0].split(':')[1]) / 15);
formateSelectedTime(timeValue, hour, part);
};
const reset = () => {
const currentElm = document.getElementById(`time-${activeIndex}`);
currentElm?.blur();
setActiveIndex(-1);
};
switch (e.key) {
case 'ArrowUp':
movePrev();
break;
case 'ArrowDown':
moveNext();
break;
case 'Escape':
reset();
break;
case 'Enter':
setElement();
break;
}
},
[activeIndex, formateSelectedTime]
);
const handleClick = React.useCallback(
(hour: number, part: number, PM_AM: string, currentIndex: number) => {
formateSelectedTime(`${hour}:${part === 0 ? '00' : timestamp * part} ${PM_AM}`, hour, part);
setActiveIndex(currentIndex);
},
[formateSelectedTime]
);
const currentTime = React.useMemo(() => {
const timeVal = Time.split(' ')[0];
return {
hours: Number.parseInt(timeVal.split(':')[0]),
minutes: Number.parseInt(timeVal.split(':')[1]),
};
}, [Time]);
React.useEffect(() => {
const getCurrentElementTime = () => {
const timeVal = Time.split(' ')[0];
const hours = Number.parseInt(timeVal.split(':')[0]);
const minutes = Number.parseInt(timeVal.split(':')[1]);
const PM_AM = Time.split(' ')[1];
const formatIndex = PM_AM === 'AM' ? hours : hours === 12 ? hours : hours + 12;
const formattedHours = formatIndex;
console.log(formatIndex);
for (let j = 0; j <= 3; j++) {
const diff = Math.abs(j * timestamp - minutes);
const selected =
PM_AM === (formattedHours >= 12 ? 'PM' : 'AM') &&
(minutes <= 53 ? diff < Math.ceil(timestamp / 2) : diff < timestamp);
if (selected) {
const trueIndex = activeIndex === -1 ? formattedHours * 4 + j : activeIndex;
setActiveIndex(trueIndex);
const currentElm = document.getElementById(`time-${trueIndex}`);
currentElm?.scrollIntoView({
block: 'center',
behavior: 'smooth',
});
}
}
};
getCurrentElementTime();
}, [Time, activeIndex]);
const height = React.useMemo(() => {
if (!document) return;
const calendarElm = document.getElementById('calendar');
if (!calendarElm) return;
return calendarElm.style.height;
}, []);
return (
<div className='space-y-2 pr-3 py-3 relative '>
<h3 className='text-sm font-medium text-center'>Time</h3>
<ScrollArea
onKeyDown={handleKeydown}
className='h-[90%] w-full focus-visible:outline-0 focus-visible:ring-0 focus-visible:ring-offset-0 focus-visible:border-0 py-0.5'
style={{
height,
}}
>
<ul className={cn('flex items-center flex-col gap-1 h-full max-h-56 w-28 px-1 py-0.5')}>
{Array.from({ length: 24 }).map((_, i) => {
const PM_AM = i >= 12 ? 'PM' : 'AM';
const formatIndex = i > 12 ? i % 12 : i === 0 || i === 12 ? 12 : i;
return Array.from({ length: 4 }).map((_, part) => {
const diff = Math.abs(part * timestamp - currentTime.minutes);
const trueIndex = i * 4 + part;
// ? refactor : add the select of the default time on the current device (H:MM)
const isSelected =
(currentTime.hours === i || currentTime.hours === formatIndex) &&
Time.split(' ')[1] === PM_AM &&
(currentTime.minutes <= 53 ? diff < Math.ceil(timestamp / 2) : diff < timestamp);
const isSuggested = !value && isSelected;
const currentValue = `${formatIndex}:${
part === 0 ? '00' : timestamp * part
} ${PM_AM}`;
return (
<li
tabIndex={isSelected ? 0 : -1}
id={`time-${trueIndex}`}
key={`time-${trueIndex}`}
aria-label='currentTime'
className={cn(
buttonVariants({
variant: isSuggested ? 'secondary' : isSelected ? 'default' : 'outline-solid',
}),
'h-8 px-3 w-full text-sm focus-visible:outline-0 outline-0 focus-visible:border-0 cursor-default ring-0'
)}
onClick={() => handleClick(i, part, PM_AM, trueIndex)}
onFocus={() => isSuggested && setActiveIndex(trueIndex)}
>
{currentValue}
</li>
);
});
})}
</ul>
</ScrollArea>
</div>
);
};
const getDefaultPlaceholder = (showCalendar: boolean, showTimePicker: boolean) => {
if (!showCalendar && showTimePicker) {
return 'e.g. "5pm" or "in 2 hours"';
}
if (showCalendar && !showTimePicker) {
return 'e.g. "tomorrow" or "next monday"';
}
return 'e.g. "tomorrow at 5pm" or "in 2 hours"';
};
const NaturalLanguageInput = React.forwardRef<
HTMLInputElement,
{
placeholder?: string;
disabled?: boolean;
}
>(({ placeholder, ...props }, ref) => {
const { value, onValueChange, Time, onTimeChange, showCalendar, showTimePicker } =
useSmartDateInput();
const _placeholder = placeholder ?? getDefaultPlaceholder(showCalendar, showTimePicker);
const [inputValue, setInputValue] = React.useState<string>('');
React.useEffect(() => {
if (!value) {
setInputValue('');
return;
}
const formattedValue = formatDateTime(value, showCalendar, showTimePicker);
setInputValue(formattedValue);
// Only update time if time picker is shown
if (showTimePicker) {
const hour = value.getHours();
const timeVal = `${hour >= 12 ? hour % 12 || 12 : hour || 12}:${String(
value.getMinutes()
).padStart(2, '0')} ${hour >= 12 ? 'PM' : 'AM'}`;
onTimeChange(timeVal);
}
}, [value, showCalendar, showTimePicker]);
const handleParse = React.useCallback(
(e: React.ChangeEvent<HTMLInputElement>) => {
const parsedDateTime = parseDateTime(e.currentTarget.value);
if (parsedDateTime) {
// If only showing time picker, preserve the current date
if (!showCalendar && showTimePicker && value) {
parsedDateTime.setFullYear(value.getFullYear(), value.getMonth(), value.getDate());
}
// If only showing calendar, preserve the current time
if (showCalendar && !showTimePicker && value) {
parsedDateTime.setHours(0, 0, 0, 0);
}
// console.log(parsedDateTime);
onValueChange(parsedDateTime);
setInputValue(formatDateTime(parsedDateTime, showCalendar, showTimePicker));
if (showTimePicker) {
const PM_AM = parsedDateTime.getHours() >= 12 ? 'PM' : 'AM';
const PM_AM_hour = parsedDateTime.getHours();
const hour =
PM_AM_hour > 12
? PM_AM_hour % 12
: PM_AM_hour === 0 || PM_AM_hour === 12
? 12
: PM_AM_hour;
onTimeChange(`${hour}:${String(parsedDateTime.getMinutes()).padStart(2, '0')} ${PM_AM}`);
}
}
},
[value, showCalendar, showTimePicker]
);
const handleKeydown = React.useCallback(
(e: React.KeyboardEvent<HTMLInputElement>) => {
if (e.key === 'Enter') {
handleParse(e as any);
}
},
[handleParse]
);
return (
<input
ref={ref}
type='text'
placeholder={_placeholder}
value={inputValue}
onChange={(e) => setInputValue(e.currentTarget.value)}
onKeyDown={handleKeydown}
onBlur={handleParse}
className={cn(
'px-2 mr-0.5 dark:bg-neutral-800 bg-neutral-50 flex-1 border-none h-8 rounded-sm',
inputBase
)}
{...props}
/>
);
});
NaturalLanguageInput.displayName = 'NaturalLanguageInput';
type DateTimeLocalInputProps = {} & CalendarProps;
const DateTimeLocalInput = ({ className, ...props }: DateTimeLocalInputProps) => {
const { value, onValueChange, Time, showCalendar, showTimePicker } = useSmartDateInput();
const formateSelectedDate = React.useCallback(
(date: Date | undefined, selectedDate: Date, m: ActiveModifiers, e: React.MouseEvent) => {
const parsedDateTime = new Date(selectedDate);
if (!showTimePicker) {
// If only calendar is shown, set time to start of day
parsedDateTime.setHours(0, 0, 0, 0);
} else if (value) {
// If time picker is shown, preserve existing time
parsedDateTime.setHours(
value.getHours(),
value.getMinutes(),
value.getSeconds(),
value.getMilliseconds()
);
}
onValueChange(parsedDateTime);
},
[value, showTimePicker, onValueChange]
);
return (
<Popover>
<PopoverTrigger asChild>
<Button
variant={'outline'}
size={'icon'}
className={cn(
'size-9 flex items-center justify-center font-normal dark:bg-neutral-800 bg-neutral-200',
!value && 'text-muted-foreground'
)}
>
<CalendarIcon className='size-4' />
<span className='sr-only'>calendar</span>
</Button>
</PopoverTrigger>
<PopoverContent className='w-auto p-0 dark:bg-neutral-800 bg-neutral-50' sideOffset={8}>
<div className='flex gap-1'>
{showCalendar && (
<Calendar
{...props}
id={'calendar'}
className={cn('peer flex justify-end', inputBase, className)}
mode='single'
selected={value}
onSelect={formateSelectedDate}
initialFocus
/>
)}
{showTimePicker && <TimePicker />}
</div>
</PopoverContent>
</Popover>
);
};
DateTimeLocalInput.displayName = 'DateTimeLocalInput';
import { Slot } from '@radix-ui/react-slot';
import { type VariantProps, cva } from 'class-variance-authority';
import * as React from 'react';
import { cn } from '@/lib/utils';
const buttonVariants = cva(
'inline-flex items-center justify-center whitespace-nowrap rounded-md text-sm font-medium ring-offset-background transition-colors focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50',
{
variants: {
variant: {
default: 'bg-primary text-primary-foreground hover:bg-primary/90',
destructive: 'bg-destructive text-destructive-foreground hover:bg-destructive/90',
outline:
'border border-input dark:bg-neutral-800 bg-neutral-50 hover:bg-accent hover:text-accent-foreground',
secondary: 'bg-secondary text-secondary-foreground hover:bg-secondary/80',
ghost: 'hover:bg-accent hover:text-accent-foreground',
link: 'text-primary underline-offset-4 hover:underline',
uilayouts:
'dark:bg-zinc-900 bg-neutral-200 dark:text-white text-black border dark:border-neutral-800',
},
size: {
default: 'h-10 px-4 py-2',
sm: 'h-9 rounded-md px-3',
lg: 'h-11 rounded-md px-8',
icon: 'h-10 w-10',
},
},
defaultVariants: {
variant: 'default',
size: 'default',
},
}
);
export interface ButtonProps
extends React.ButtonHTMLAttributes<HTMLButtonElement>,
VariantProps<typeof buttonVariants> {
asChild?: boolean;
}
const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
({ className, variant, size, asChild = false, ...props }, ref) => {
const Comp = asChild ? Slot : 'button';
return (
<Comp className={cn(buttonVariants({ variant, size, className }))} ref={ref} {...props} />
);
}
);
Button.displayName = 'Button';
export { Button, buttonVariants };
'use client';
import { buttonVariants } from '@/components/website/ui/button';
import { cn } from '@/lib/utils';
import { ChevronLeft, ChevronRight } from 'lucide-react';
import type * as React from 'react';
import { DayPicker } from 'react-day-picker';
export type CalendarProps = React.ComponentProps<typeof DayPicker>;
function Calendar({ className, classNames, showOutsideDays = true, ...props }: CalendarProps) {
return (
<DayPicker
showOutsideDays={showOutsideDays}
className={cn('p-3', className)}
classNames={{
months: 'flex flex-col sm:flex-row space-y-4 sm:space-x-4 sm:space-y-0',
month: 'space-y-4',
caption: 'flex justify-center pt-1 relative items-center',
caption_label: 'text-sm font-medium',
nav: 'space-x-1 flex items-center',
nav_button: cn(
buttonVariants({ variant: 'outline' }),
'h-7 w-7 bg-transparent p-0 opacity-50 hover:opacity-100'
),
nav_button_previous: 'absolute left-1',
nav_button_next: 'absolute right-1',
table: 'w-full border-collapse space-y-1',
head_row: 'flex',
head_cell: 'text-muted-foreground rounded-md w-9 font-normal text-[0.8rem]',
row: 'flex w-full mt-2',
cell: 'h-9 w-9 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',
day: cn(
buttonVariants({ variant: 'ghost' }),
'h-9 w-9 p-0 font-normal aria-selected:opacity-100'
),
day_range_end: 'day-range-end',
day_selected:
'bg-primary text-primary-foreground hover:bg-primary hover:text-primary-foreground focus:bg-primary focus:text-primary-foreground',
day_today: 'bg-accent text-accent-foreground',
day_outside:
'day-outside text-muted-foreground opacity-50 aria-selected:bg-accent/50 aria-selected:text-muted-foreground aria-selected:opacity-30',
day_disabled: 'text-muted-foreground opacity-50',
day_range_middle: 'aria-selected:bg-accent aria-selected:text-accent-foreground',
day_hidden: 'invisible',
...classNames,
}}
components={{
IconLeft: ({ ...props }) => <ChevronLeft className='h-4 w-4' />,
IconRight: ({ ...props }) => <ChevronRight className='h-4 w-4' />,
}}
{...props}
/>
);
}
Calendar.displayName = 'Calendar';
export { Calendar };
'use client';
import * as PopoverPrimitive from '@radix-ui/react-popover';
import * as React from 'react';
import { cn } from '@/lib/utils';
const Popover = PopoverPrimitive.Root;
const PopoverTrigger = PopoverPrimitive.Trigger;
const PopoverContent = React.forwardRef<
React.ElementRef<typeof PopoverPrimitive.Content>,
React.ComponentPropsWithoutRef<typeof PopoverPrimitive.Content>
>(({ className, align = 'center', sideOffset = 4, ...props }, ref) => (
<PopoverPrimitive.Portal>
<PopoverPrimitive.Content
ref={ref}
align={align}
sideOffset={sideOffset}
className={cn(
'z-50 w-72 rounded-md border bg-popover p-4 text-popover-foreground shadow-md outline-hidden data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2',
className
)}
{...props}
/>
</PopoverPrimitive.Portal>
));
PopoverContent.displayName = PopoverPrimitive.Content.displayName;
export { Popover, PopoverContent, PopoverTrigger };
'use client';
import * as ScrollAreaPrimitive from '@radix-ui/react-scroll-area';
import * as React from 'react';
import { cn } from '@/lib/utils';
const ScrollArea = React.forwardRef<
React.ElementRef<typeof ScrollAreaPrimitive.Root>,
React.ComponentPropsWithoutRef<typeof ScrollAreaPrimitive.Root>
>(({ className, children, ...props }, ref) => (
<ScrollAreaPrimitive.Root
ref={ref}
className={cn('relative overflow-hidden', className)}
{...props}
>
<ScrollAreaPrimitive.Viewport className='h-full w-full rounded-[inherit]'>
{children}
</ScrollAreaPrimitive.Viewport>
<ScrollBar />
<ScrollAreaPrimitive.Corner />
</ScrollAreaPrimitive.Root>
));
ScrollArea.displayName = ScrollAreaPrimitive.Root.displayName;
const ScrollBar = React.forwardRef<
React.ElementRef<typeof ScrollAreaPrimitive.ScrollAreaScrollbar>,
React.ComponentPropsWithoutRef<typeof ScrollAreaPrimitive.ScrollAreaScrollbar>
>(({ className, orientation = 'vertical', ...props }, ref) => (
<ScrollAreaPrimitive.ScrollAreaScrollbar
ref={ref}
orientation={orientation}
className={cn(
'flex touch-none select-none transition-colors',
orientation === 'vertical' && 'h-full w-2.5 border-l border-l-transparent p-px',
orientation === 'horizontal' && 'h-2.5 flex-col border-t border-t-transparent p-px',
className
)}
{...props}
>
<ScrollAreaPrimitive.ScrollAreaThumb className='relative flex-1 rounded-full dark:bg-neutral-800 bg-neutral-200' />
</ScrollAreaPrimitive.ScrollAreaScrollbar>
));
ScrollBar.displayName = ScrollAreaPrimitive.ScrollAreaScrollbar.displayName;
export { ScrollArea, ScrollBar };