* chore(): Update dependencies usage * chore(): Update dependencies usage * chore(): Update dependencies usage * chore(): Update dependencies usage * chore(): Update dependencies usage * chore(): Update dependencies usage * chore(): Update dependencies usage * chore(): Update dependencies usage * fix for wxios 1.6 * chore(): Update dependencies usage * chore(): Update dependencies usage * chore(): Update dependencies usage * chore(): Update dependencies usage * chore(): Update dependencies usage * chore(): Update dependencies usage * chore(): Update dependencies usage * chore(): Update dependencies usage * chore(): Update dependencies usage * chore(): Update dependencies usage * push scripts * update build concurrency * chore(): Update dependencies usage * chore(): Update dependencies usage * chore(): Update dependencies usage * chore(): Update dependencies usage * chore(): Update dependencies usage * fixes * update yarn * fixes * fix script * Create heavy-suns-tickle.md * update changeset --------- Co-authored-by: Carlos R. L. Rodrigues <37986729+carlos-r-l-rodrigues@users.noreply.github.com>
246 lines
6.5 KiB
TypeScript
246 lines
6.5 KiB
TypeScript
"use client"
|
|
|
|
import {
|
|
CalendarDate,
|
|
CalendarDateTime,
|
|
getLocalTimeZone,
|
|
} from "@internationalized/date"
|
|
import { CalendarMini, Clock, XMarkMini } from "@medusajs/icons"
|
|
import { cva } from "cva"
|
|
import * as React from "react"
|
|
import {
|
|
DateValue,
|
|
TimeValue,
|
|
useDatePicker,
|
|
useInteractOutside,
|
|
type AriaDatePickerProps as BaseDatePickerProps,
|
|
} from "react-aria"
|
|
import { useDatePickerState } from "react-stately"
|
|
|
|
import { InternalCalendar } from "@/components/calendar"
|
|
import { Popover } from "@/components/popover"
|
|
import { TimeInput } from "@/components/time-input"
|
|
import { Granularity } from "@/types"
|
|
import {
|
|
createCalendarDateFromDate,
|
|
getDefaultCalendarDateFromDate,
|
|
updateCalendarDateFromDate,
|
|
} from "@/utils/calendar"
|
|
import { clx } from "@/utils/clx"
|
|
|
|
import { DatePickerButton } from "./date-picker-button"
|
|
import { DatePickerClearButton } from "./date-picker-clear-button"
|
|
import { DatePickerField } from "./date-picker-field"
|
|
|
|
type DatePickerValueProps = {
|
|
defaultValue?: Date | null
|
|
value?: Date | null
|
|
onChange?: (value: Date | null) => void
|
|
isDateUnavailable?: (date: Date) => boolean
|
|
minValue?: Date
|
|
maxValue?: Date
|
|
shouldCloseOnSelect?: boolean
|
|
granularity?: Granularity
|
|
size?: "base" | "small"
|
|
className?: string
|
|
modal?: boolean
|
|
}
|
|
|
|
interface DatePickerProps
|
|
extends Omit<
|
|
BaseDatePickerProps<CalendarDateTime | CalendarDate>,
|
|
keyof DatePickerValueProps
|
|
>,
|
|
DatePickerValueProps {}
|
|
|
|
const datePickerStyles = (
|
|
isOpen: boolean,
|
|
isInvalid: boolean,
|
|
value?: DateValue | null
|
|
) =>
|
|
cva({
|
|
base: clx(
|
|
"bg-ui-bg-field shadow-borders-base txt-compact-small text-ui-fg-base transition-fg grid items-center gap-2 overflow-hidden rounded-md h-fit",
|
|
"focus-within:shadow-borders-interactive-with-active focus-visible:shadow-borders-interactive-with-active",
|
|
"aria-[invalid=true]:shadow-borders-error invalid:shadow-borders-error",
|
|
{
|
|
"shadow-borders-interactive-with-active": isOpen,
|
|
"shadow-borders-error": isInvalid,
|
|
"pr-2": !value,
|
|
}
|
|
),
|
|
variants: {
|
|
size: {
|
|
small: clx("grid-cols-[28px_1fr]", {
|
|
"grid-cols-[28px_1fr_28px]": !!value,
|
|
}),
|
|
base: clx("grid-cols-[32px_1fr]", {
|
|
"grid-cols-[32px_1fr_32px]": !!value,
|
|
}),
|
|
},
|
|
},
|
|
})
|
|
|
|
const HAS_TIME = new Set<Granularity>(["hour", "minute", "second"])
|
|
|
|
const DatePicker = React.forwardRef<HTMLDivElement, DatePickerProps>(
|
|
(
|
|
{
|
|
size = "base",
|
|
shouldCloseOnSelect = true,
|
|
className,
|
|
modal = false,
|
|
...props
|
|
},
|
|
ref
|
|
) => {
|
|
const [value, setValue] = React.useState<
|
|
CalendarDateTime | CalendarDate | null | undefined
|
|
>(
|
|
getDefaultCalendarDateFromDate(
|
|
props.value,
|
|
props.defaultValue,
|
|
props.granularity
|
|
)
|
|
)
|
|
|
|
const innerRef = React.useRef<HTMLDivElement>(null)
|
|
React.useImperativeHandle(ref, () => innerRef.current as HTMLDivElement)
|
|
|
|
const contentRef = React.useRef<HTMLDivElement>(null)
|
|
|
|
const _props = convertProps(props, setValue)
|
|
|
|
const state = useDatePickerState({
|
|
..._props,
|
|
shouldCloseOnSelect,
|
|
})
|
|
|
|
const { groupProps, fieldProps, buttonProps, dialogProps, calendarProps } =
|
|
useDatePicker(_props, state, innerRef)
|
|
|
|
React.useEffect(() => {
|
|
setValue(
|
|
props.value
|
|
? updateCalendarDateFromDate(value, props.value, props.granularity)
|
|
: null
|
|
)
|
|
state.setValue(
|
|
props.value
|
|
? updateCalendarDateFromDate(value, props.value, props.granularity)
|
|
: null
|
|
)
|
|
}, [props.value])
|
|
|
|
function clear(e: React.MouseEvent<HTMLButtonElement>) {
|
|
e.preventDefault()
|
|
e.stopPropagation()
|
|
|
|
props.onChange?.(null)
|
|
state.setValue(null)
|
|
}
|
|
|
|
useInteractOutside({
|
|
ref: contentRef,
|
|
onInteractOutside: () => {
|
|
state.setOpen(false)
|
|
},
|
|
})
|
|
|
|
const hasTime = props.granularity && HAS_TIME.has(props.granularity)
|
|
const Icon = hasTime ? Clock : CalendarMini
|
|
|
|
return (
|
|
<Popover modal={modal} open={state.isOpen} onOpenChange={state.setOpen}>
|
|
<Popover.Anchor asChild>
|
|
<div
|
|
ref={ref}
|
|
className={clx(
|
|
datePickerStyles(
|
|
state.isOpen,
|
|
state.isInvalid,
|
|
state.value
|
|
)({ size }),
|
|
className
|
|
)}
|
|
{...groupProps}
|
|
>
|
|
<DatePickerButton {...buttonProps} size={size}>
|
|
<Icon />
|
|
</DatePickerButton>
|
|
<DatePickerField {...fieldProps} size={size} />
|
|
{!!state.value && (
|
|
<DatePickerClearButton onClick={clear}>
|
|
<XMarkMini />
|
|
</DatePickerClearButton>
|
|
)}
|
|
</div>
|
|
</Popover.Anchor>
|
|
<Popover.Content
|
|
ref={contentRef}
|
|
{...dialogProps}
|
|
className="flex flex-col divide-y p-0"
|
|
>
|
|
<div className="p-3">
|
|
<InternalCalendar autoFocus {...calendarProps} />
|
|
</div>
|
|
{state.hasTime && (
|
|
<div className="p-3">
|
|
<TimeInput
|
|
value={state.timeValue}
|
|
onChange={(value) => state.setTimeValue(value as TimeValue)}
|
|
hourCycle={props.hourCycle}
|
|
/>
|
|
</div>
|
|
)}
|
|
</Popover.Content>
|
|
</Popover>
|
|
)
|
|
}
|
|
)
|
|
DatePicker.displayName = "DatePicker"
|
|
|
|
function convertProps(
|
|
props: DatePickerProps,
|
|
setValue: React.Dispatch<
|
|
React.SetStateAction<CalendarDateTime | CalendarDate | null | undefined>
|
|
>
|
|
): BaseDatePickerProps<CalendarDateTime | CalendarDate> {
|
|
const {
|
|
minValue,
|
|
maxValue,
|
|
isDateUnavailable: _isDateUnavailable,
|
|
onChange: _onChange,
|
|
value: __value__,
|
|
defaultValue: __defaultValue__,
|
|
...rest
|
|
} = props
|
|
|
|
const onChange = (value: CalendarDateTime | CalendarDateTime | null) => {
|
|
setValue(value)
|
|
_onChange?.(value ? value.toDate(getLocalTimeZone()) : null)
|
|
}
|
|
|
|
const isDateUnavailable = (date: DateValue) => {
|
|
const _date = date.toDate(getLocalTimeZone())
|
|
|
|
return _isDateUnavailable ? _isDateUnavailable(_date) : false
|
|
}
|
|
|
|
return {
|
|
...rest,
|
|
onChange: onChange as BaseDatePickerProps<
|
|
CalendarDateTime | CalendarDate
|
|
>["onChange"],
|
|
isDateUnavailable,
|
|
minValue: minValue
|
|
? createCalendarDateFromDate(minValue, props.granularity)
|
|
: minValue,
|
|
maxValue: maxValue
|
|
? createCalendarDateFromDate(maxValue, props.granularity)
|
|
: maxValue,
|
|
}
|
|
}
|
|
|
|
export { DatePicker, type Granularity }
|