fix(ui,dashboard): Revamp DatePicker component (#7891)
**What** - Revamps the DatePicker component. - Addresses all issues with broken DatePickers across admin. **Note** - Part of this PR is adding a I18nProvider which is used to set the locale that is used for our DatePicker and Calendar components. Per default they use the browser locale. In the current implementation, we are grabbing the locale to use from the language that is picked in the "Profile" section. This means that currently the only possible locale is "en-US", meaning times uses AM/PM. This is likely not what we want, but we need to make a decision on how we want to handle this globally, will create a ticket for it and we can then clean it up later on. - This PR does not include "presets" or a DateRange picker that were part of the old implementation. Will open tickets to re-add this later on, but since we aren't using it in admin any where it makes sense to address later. - This PR also bumps and pin every `@radix-ui` dependency in `@medusajs/ui` and `@medusajs/dashboard`. Our different versions were pulling in multiple versions of internal radix dependencies which were breaking Popover and Dialog behaviour across admin. One thing to note is that Radix have started to print warnings for missing Descriptions and Titles in dialogs. We should add these as we go, for better accessibility. Its not an urgent task but something we can add as we clean up admin over the following weeks. CLOSES CORE-2382
This commit is contained in:
committed by
GitHub
parent
074e4a888e
commit
a84e5a6ced
@@ -1,56 +0,0 @@
|
||||
import type { Meta, StoryObj } from "@storybook/react"
|
||||
import * as React from "react"
|
||||
|
||||
import { isBrowserLocaleClockType24h } from "../../utils/is-browser-locale-hour-cycle-24h"
|
||||
import { TimeInput } from "./time-input"
|
||||
|
||||
const meta: Meta<typeof TimeInput> = {
|
||||
title: "Components/TimeInput",
|
||||
component: TimeInput,
|
||||
parameters: {
|
||||
layout: "centered",
|
||||
},
|
||||
render: (args) => (
|
||||
<div className="w-[300px]">
|
||||
<TimeInput aria-labelledby="time" {...args} />
|
||||
</div>
|
||||
),
|
||||
}
|
||||
|
||||
export default meta
|
||||
|
||||
type Story = StoryObj<typeof TimeInput>
|
||||
|
||||
// Hour Cycle defaults to your browser's locale.
|
||||
export const Default: Story = {
|
||||
render: (args) => {
|
||||
return (
|
||||
<div className="flex flex-col gap-y-4">
|
||||
<TimeInput aria-labelledby="time" {...args} />
|
||||
<div className="flex flex-col gap-y-2">
|
||||
<p className="text-xs">
|
||||
Will use 24h or 12h cycle depending on your locale
|
||||
</p>
|
||||
<div className="text-xs">
|
||||
<pre>
|
||||
Locale: {window.navigator.language}, Hour Cycle:{" "}
|
||||
{isBrowserLocaleClockType24h() ? "24h" : "12h"}
|
||||
</pre>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
},
|
||||
}
|
||||
|
||||
export const Hour24: Story = {
|
||||
args: {
|
||||
hourCycle: 24,
|
||||
},
|
||||
}
|
||||
|
||||
export const Hour12: Story = {
|
||||
args: {
|
||||
hourCycle: 12,
|
||||
},
|
||||
}
|
||||
@@ -1,131 +1,44 @@
|
||||
"use client"
|
||||
|
||||
import * as React from "react"
|
||||
import {
|
||||
AriaTimeFieldProps,
|
||||
TimeValue,
|
||||
useDateSegment,
|
||||
useLocale,
|
||||
useTimeField,
|
||||
} from "@react-aria/datepicker"
|
||||
import {
|
||||
useTimeFieldState,
|
||||
type DateFieldState,
|
||||
type DateSegment,
|
||||
} from "@react-stately/datepicker"
|
||||
import * as React from "react"
|
||||
} from "react-aria"
|
||||
import { useTimeFieldState } from "react-stately"
|
||||
|
||||
import { inputBaseStyles } from "@/components/input"
|
||||
import { DateSegment } from "@/components/date-segment"
|
||||
import { clx } from "@/utils/clx"
|
||||
|
||||
type TimeSegmentProps = {
|
||||
segment: DateSegment
|
||||
state: DateFieldState
|
||||
}
|
||||
|
||||
const TimeSegment = ({ segment, state }: TimeSegmentProps) => {
|
||||
const TimeInput = (props: AriaTimeFieldProps<TimeValue>) => {
|
||||
const ref = React.useRef<HTMLDivElement>(null)
|
||||
|
||||
const { segmentProps } = useDateSegment(segment, state, ref)
|
||||
|
||||
const isColon = segment.type === "literal" && segment.text === ":"
|
||||
const isSpace = segment.type === "literal" && segment.text === " "
|
||||
|
||||
const isDecorator = isColon || isSpace
|
||||
const { locale } = useLocale()
|
||||
const state = useTimeFieldState({
|
||||
...props,
|
||||
locale,
|
||||
})
|
||||
const { fieldProps } = useTimeField(props, state, ref)
|
||||
|
||||
return (
|
||||
<div
|
||||
{...segmentProps}
|
||||
ref={ref}
|
||||
{...fieldProps}
|
||||
aria-label="Time input"
|
||||
className={clx(
|
||||
"txt-compact-small w-full rounded-md px-2 py-1 text-left uppercase tabular-nums",
|
||||
inputBaseStyles,
|
||||
"group-aria-[invalid=true]/time-input:!shadow-borders-error group-invalid/time-input:!shadow-borders-error",
|
||||
"bg-ui-bg-field shadow-borders-base txt-compact-small flex items-center rounded-md px-2 py-1",
|
||||
{
|
||||
"text-ui-fg-muted !w-fit border-none bg-transparent px-0 shadow-none":
|
||||
isDecorator,
|
||||
hidden: isSpace,
|
||||
"text-ui-fg-disabled bg-ui-bg-disabled border-ui-border-base shadow-none":
|
||||
state.isDisabled,
|
||||
"!text-ui-fg-muted !bg-transparent": !segment.isEditable,
|
||||
"": props.isDisabled,
|
||||
}
|
||||
)}
|
||||
>
|
||||
<span
|
||||
aria-hidden="true"
|
||||
className={clx(
|
||||
"txt-compact-small text-ui-fg-muted pointer-events-none block w-full text-left",
|
||||
{
|
||||
hidden: !segment.isPlaceholder,
|
||||
"h-0": !segment.isPlaceholder,
|
||||
}
|
||||
)}
|
||||
>
|
||||
{segment.placeholder}
|
||||
</span>
|
||||
{segment.isPlaceholder ? "" : segment.text}
|
||||
{state.segments.map((segment, index) => {
|
||||
return <DateSegment key={index} segment={segment} state={state} />
|
||||
})}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
type TimeInputProps = Omit<
|
||||
AriaTimeFieldProps<TimeValue>,
|
||||
"label" | "shouldForceLeadingZeros" | "description" | "errorMessage"
|
||||
>
|
||||
|
||||
/**
|
||||
* This component is based on the `div` element and supports all of its props.
|
||||
*/
|
||||
const TimeInput = React.forwardRef<HTMLDivElement, TimeInputProps>(
|
||||
(
|
||||
{
|
||||
/**
|
||||
* The time's format. If no value is specified, the format is
|
||||
* set based on the user's locale.
|
||||
*/
|
||||
hourCycle,
|
||||
...props
|
||||
}: TimeInputProps,
|
||||
ref
|
||||
) => {
|
||||
const innerRef = React.useRef<HTMLDivElement>(null)
|
||||
|
||||
React.useImperativeHandle<HTMLDivElement | null, HTMLDivElement | null>(
|
||||
ref,
|
||||
() => innerRef?.current
|
||||
)
|
||||
|
||||
const locale = window !== undefined ? window.navigator.language : "en-US"
|
||||
|
||||
const state = useTimeFieldState({
|
||||
hourCycle: hourCycle,
|
||||
locale: locale,
|
||||
shouldForceLeadingZeros: true,
|
||||
autoFocus: true,
|
||||
...props,
|
||||
})
|
||||
|
||||
const { fieldProps } = useTimeField(
|
||||
{
|
||||
...props,
|
||||
hourCycle: hourCycle,
|
||||
shouldForceLeadingZeros: true,
|
||||
},
|
||||
state,
|
||||
innerRef
|
||||
)
|
||||
|
||||
return (
|
||||
<div
|
||||
{...fieldProps}
|
||||
ref={innerRef}
|
||||
className="group/time-input inline-flex w-full gap-x-2"
|
||||
>
|
||||
{state.segments.map((segment, i) => (
|
||||
<TimeSegment key={i} segment={segment} state={state} />
|
||||
))}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
)
|
||||
TimeInput.displayName = "TimeInput"
|
||||
|
||||
export { TimeInput }
|
||||
|
||||
Reference in New Issue
Block a user