Skip to content

Commit

Permalink
feat(dashboard,ui): DateFilter should open correctly (medusajs#9775)
Browse files Browse the repository at this point in the history
  • Loading branch information
kasperkristensen authored Oct 24, 2024
1 parent 92bbd79 commit 59e6747
Show file tree
Hide file tree
Showing 3 changed files with 143 additions and 99 deletions.
6 changes: 6 additions & 0 deletions .changeset/perfect-ravens-allow.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
"@medusajs/dashboard": patch
"@medusajs/ui": patch
---

fix(dashboard,ui): DateFilter should remain open
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,8 @@ import { useTranslation } from "react-i18next"
import { useDate } from "../../../../hooks/use-date"
import { useSelectedParams } from "../hooks"
import { useDataTableFilterContext } from "./context"
import { IFilter } from "./types"
import FilterChip from "./filter-chip"
import { IFilter } from "./types"

type DateFilterProps = IFilter

Expand Down Expand Up @@ -97,7 +97,9 @@ export const DateFilter = ({

const displayValue = getDisplayValueFromPresets() || getCustomDisplayValue()

const [previousValue, setPreviousValue] = useState<string | undefined>(displayValue)
const [previousValue, setPreviousValue] = useState<string | undefined>(
displayValue
)

const handleRemove = () => {
selectedParams.delete()
Expand Down Expand Up @@ -210,6 +212,7 @@ export const DateFilter = ({
</div>
<div className="px-2 py-1">
<DatePicker
modal
maxValue={customEndValue}
value={customStartValue}
onChange={(d) => handleCustomDateChange(d, "start")}
Expand All @@ -224,6 +227,7 @@ export const DateFilter = ({
</div>
<div className="px-2 py-1">
<DatePicker
modal
minValue={customStartValue}
value={customEndValue || undefined}
onChange={(d) => {
Expand Down
228 changes: 131 additions & 97 deletions packages/design-system/ui/src/components/date-picker/date-picker.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
"use client"

import { CalendarDate, CalendarDateTime, getLocalTimeZone } from "@internationalized/date"
import {
CalendarDate,
CalendarDateTime,
getLocalTimeZone,
} from "@internationalized/date"
import { CalendarMini, Clock, XMarkMini } from "@medusajs/icons"
import { cva } from "cva"
import * as React from "react"
Expand Down Expand Up @@ -38,10 +42,14 @@ type DatePickerValueProps = {
granularity?: Granularity
size?: "base" | "small"
className?: string
modal?: boolean
}

interface DatePickerProps
extends Omit<BaseDatePickerProps<CalendarDateTime | CalendarDate>, keyof DatePickerValueProps>,
extends Omit<
BaseDatePickerProps<CalendarDateTime | CalendarDate>,
keyof DatePickerValueProps
>,
DatePickerValueProps {}

const datePickerStyles = (
Expand Down Expand Up @@ -72,103 +80,123 @@ const datePickerStyles = (
},
})

const HAS_TIME = new Set<Granularity>(["hour","minute", "second"])

const DatePicker = React.forwardRef<HTMLDivElement, DatePickerProps>(({
size = "base",
shouldCloseOnSelect = true,
className,
...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()
const HAS_TIME = new Set<Granularity>(["hour", "minute", "second"])

props.onChange?.(null)
state.setValue(null)
}

useInteractOutside({
ref: contentRef,
onInteractOutside: () => {
state.setOpen(false)
const DatePicker = React.forwardRef<HTMLDivElement, DatePickerProps>(
(
{
size = "base",
shouldCloseOnSelect = true,
className,
modal = false,
...props
},
})

const hasTime = props.granularity && HAS_TIME.has(props.granularity)
const Icon = hasTime ? Clock : CalendarMini

return (
<Popover open={state.isOpen} onOpenChange={state.setOpen}>
<Popover.Anchor asChild>
<div
ref={ref}
className={clx(
datePickerStyles(
state.isOpen,
state.isInvalid,
state.value
)({ size }),
className
)}
{...groupProps}
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"
>
<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={state.setTimeValue}
hourCycle={props.hourCycle}
/>
<InternalCalendar autoFocus {...calendarProps} />
</div>
)}
</Popover.Content>
</Popover>
)
})
{state.hasTime && (
<div className="p-3">
<TimeInput
value={state.timeValue}
onChange={state.setTimeValue}
hourCycle={props.hourCycle}
/>
</div>
)}
</Popover.Content>
</Popover>
)
}
)
DatePicker.displayName = "DatePicker"

function convertProps(
Expand Down Expand Up @@ -200,10 +228,16 @@ function convertProps(

return {
...rest,
onChange: onChange as BaseDatePickerProps<CalendarDateTime | CalendarDate>["onChange"],
onChange: onChange as BaseDatePickerProps<
CalendarDateTime | CalendarDate
>["onChange"],
isDateUnavailable,
minValue: minValue ? createCalendarDateFromDate(minValue, props.granularity) : minValue,
maxValue: maxValue ? createCalendarDateFromDate(maxValue, props.granularity) : maxValue,
minValue: minValue
? createCalendarDateFromDate(minValue, props.granularity)
: minValue,
maxValue: maxValue
? createCalendarDateFromDate(maxValue, props.granularity)
: maxValue,
}
}

Expand Down

0 comments on commit 59e6747

Please sign in to comment.