Files
medusa-store/packages/design-system/ui/src/components/date-picker/date-picker.tsx
Adrien de Peretti 224ab39a81 chore(): Update dependencies usage (#13910)
* 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>
2025-10-31 16:44:14 +01:00

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 }