✨(DatePicker) add classic variant and hideLabel props
- Add variant prop to DatePicker and DateRangePicker - Add hideLabel prop for accessible hidden labels - Label(s) rendered outside wrapper in classic mode - DateRangePicker shows both labels above fields in classic mode - Compact height in classic mode - Add unit tests and Storybook stories Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -13,6 +13,7 @@ import {
|
|||||||
import { createCalendar, DateValue } from "@internationalized/date";
|
import { createCalendar, DateValue } from "@internationalized/date";
|
||||||
import classNames from "classnames";
|
import classNames from "classnames";
|
||||||
import { LabelledBox, Props } from ":/components/Forms/LabelledBox";
|
import { LabelledBox, Props } from ":/components/Forms/LabelledBox";
|
||||||
|
import type { FieldVariant } from ":/components/Forms/types";
|
||||||
|
|
||||||
interface DateSegmentProps {
|
interface DateSegmentProps {
|
||||||
currentSegment: DateSegment;
|
currentSegment: DateSegment;
|
||||||
@@ -68,10 +69,15 @@ const DateField = (props: AriaDatePickerProps<DateValue>) => {
|
|||||||
|
|
||||||
interface DateFieldBoxProps
|
interface DateFieldBoxProps
|
||||||
extends Props,
|
extends Props,
|
||||||
Omit<AriaDatePickerProps<DateValue>, "label"> {}
|
Omit<AriaDatePickerProps<DateValue>, "label"> {
|
||||||
|
variant?: FieldVariant;
|
||||||
|
}
|
||||||
|
|
||||||
const DateFieldBox = ({ ...props }: DateFieldBoxProps) => (
|
const DateFieldBox = ({
|
||||||
<LabelledBox {...props}>
|
variant = "floating",
|
||||||
|
...props
|
||||||
|
}: DateFieldBoxProps) => (
|
||||||
|
<LabelledBox {...props} variant={variant}>
|
||||||
<div
|
<div
|
||||||
className={classNames("c__date-picker__inner", {
|
className={classNames("c__date-picker__inner", {
|
||||||
"c__date-picker__inner--collapsed": props.labelAsPlaceholder,
|
"c__date-picker__inner--collapsed": props.labelAsPlaceholder,
|
||||||
|
|||||||
@@ -1459,4 +1459,82 @@ describe("<DatePicker/>", () => {
|
|||||||
document.querySelector(".c__field.my-custom-class"),
|
document.querySelector(".c__field.my-custom-class"),
|
||||||
).toBeInTheDocument();
|
).toBeInTheDocument();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe("classic variant", () => {
|
||||||
|
it("renders with classic variant", async () => {
|
||||||
|
render(
|
||||||
|
<CunninghamProvider>
|
||||||
|
<DatePicker label="Pick a date" variant="classic" />
|
||||||
|
</CunninghamProvider>,
|
||||||
|
);
|
||||||
|
// In classic mode, label is rendered outside the wrapper with its own class
|
||||||
|
expect(
|
||||||
|
document.querySelector(".c__date-picker__label"),
|
||||||
|
).toBeInTheDocument();
|
||||||
|
|
||||||
|
expect(
|
||||||
|
document.querySelector(".c__date-picker--classic"),
|
||||||
|
).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("label is always static in classic variant", async () => {
|
||||||
|
const user = userEvent.setup();
|
||||||
|
render(
|
||||||
|
<CunninghamProvider>
|
||||||
|
<DatePicker label="Pick a date" variant="classic" />
|
||||||
|
</CunninghamProvider>,
|
||||||
|
);
|
||||||
|
|
||||||
|
const label = screen.getByText("Pick a date");
|
||||||
|
|
||||||
|
// In classic variant, label is outside the wrapper with c__date-picker__label class
|
||||||
|
expect(label.classList.contains("c__date-picker__label")).toBe(true);
|
||||||
|
|
||||||
|
// Open calendar
|
||||||
|
const toggleButton = (await screen.findAllByRole("button"))![1];
|
||||||
|
await user.click(toggleButton);
|
||||||
|
|
||||||
|
// Label should still have the same class
|
||||||
|
expect(label.classList.contains("c__date-picker__label")).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("defaults to floating variant", () => {
|
||||||
|
render(
|
||||||
|
<CunninghamProvider>
|
||||||
|
<DatePicker label="Pick a date" />
|
||||||
|
</CunninghamProvider>,
|
||||||
|
);
|
||||||
|
expect(
|
||||||
|
document.querySelector(".c__date-picker--classic"),
|
||||||
|
).not.toBeInTheDocument();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("hideLabel", () => {
|
||||||
|
it("hides label visually but keeps it accessible in floating variant", () => {
|
||||||
|
render(
|
||||||
|
<CunninghamProvider>
|
||||||
|
<DatePicker label="Pick a date" hideLabel />
|
||||||
|
</CunninghamProvider>,
|
||||||
|
);
|
||||||
|
// Label should be visually hidden via LabelledBox
|
||||||
|
const label = screen.getByText("Pick a date");
|
||||||
|
expect(label.closest("label")).toHaveClass("c__offscreen");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("hides label visually but keeps it accessible in classic variant", () => {
|
||||||
|
render(
|
||||||
|
<CunninghamProvider>
|
||||||
|
<DatePicker label="Pick a date" variant="classic" hideLabel />
|
||||||
|
</CunninghamProvider>,
|
||||||
|
);
|
||||||
|
// Label should be visually hidden with c__offscreen class
|
||||||
|
const label = screen.getByText("Pick a date");
|
||||||
|
expect(label).toHaveClass("c__offscreen");
|
||||||
|
// The visible label class should not be present
|
||||||
|
expect(
|
||||||
|
document.querySelector(".c__date-picker__label:not(.c__offscreen)"),
|
||||||
|
).not.toBeInTheDocument();
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -51,6 +51,7 @@ export const DatePicker = (props: DatePickerProps) => {
|
|||||||
ref,
|
ref,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const isClassic = props.variant === "classic";
|
||||||
const labelAsPlaceholder = useMemo(
|
const labelAsPlaceholder = useMemo(
|
||||||
() => !isFocused && !pickerState.isOpen && !pickerState.value,
|
() => !isFocused && !pickerState.isOpen && !pickerState.value,
|
||||||
[pickerState.value, pickerState.isOpen, isFocused],
|
[pickerState.value, pickerState.isOpen, isFocused],
|
||||||
@@ -74,8 +75,12 @@ export const DatePicker = (props: DatePickerProps) => {
|
|||||||
<DateFieldBox
|
<DateFieldBox
|
||||||
{...{
|
{...{
|
||||||
...fieldProps,
|
...fieldProps,
|
||||||
label: props.label,
|
// In classic mode, label is rendered outside by DatePickerAux
|
||||||
labelAsPlaceholder,
|
label: isClassic ? undefined : props.label,
|
||||||
|
variant: props.variant,
|
||||||
|
hideLabel: isClassic ? undefined : props.hideLabel,
|
||||||
|
// In classic mode, always show date segments (never collapse them)
|
||||||
|
labelAsPlaceholder: isClassic ? false : labelAsPlaceholder,
|
||||||
onFocusChange: setIsFocused,
|
onFocusChange: setIsFocused,
|
||||||
disabled: props.disabled,
|
disabled: props.disabled,
|
||||||
}}
|
}}
|
||||||
|
|||||||
@@ -14,17 +14,23 @@ import { I18nProvider } from "@react-aria/i18n";
|
|||||||
import { Button } from ":/components/Button";
|
import { Button } from ":/components/Button";
|
||||||
import { Popover } from ":/components/Popover";
|
import { Popover } from ":/components/Popover";
|
||||||
import { Field, FieldProps } from ":/components/Forms/Field";
|
import { Field, FieldProps } from ":/components/Forms/Field";
|
||||||
|
import { ClassicLabel } from ":/components/Forms/ClassicLabel";
|
||||||
import { useCunningham } from ":/components/Provider";
|
import { useCunningham } from ":/components/Provider";
|
||||||
import {
|
import {
|
||||||
Calendar,
|
Calendar,
|
||||||
CalendarRange,
|
CalendarRange,
|
||||||
} from ":/components/Forms/DatePicker/Calendar";
|
} from ":/components/Forms/DatePicker/Calendar";
|
||||||
import { convertDateValueToString } from ":/components/Forms/DatePicker/utils";
|
import { convertDateValueToString } from ":/components/Forms/DatePicker/utils";
|
||||||
|
import type { FieldVariant } from ":/components/Forms/types";
|
||||||
|
|
||||||
export type DatePickerAuxSubProps = FieldProps & {
|
export type DatePickerAuxSubProps = FieldProps & {
|
||||||
// eslint-disable-next-line react/no-unused-prop-types
|
// eslint-disable-next-line react/no-unused-prop-types
|
||||||
label?: string;
|
label?: string;
|
||||||
// eslint-disable-next-line react/no-unused-prop-types
|
// eslint-disable-next-line react/no-unused-prop-types
|
||||||
|
variant?: FieldVariant;
|
||||||
|
// eslint-disable-next-line react/no-unused-prop-types
|
||||||
|
hideLabel?: boolean;
|
||||||
|
// eslint-disable-next-line react/no-unused-prop-types
|
||||||
minValue?: string;
|
minValue?: string;
|
||||||
// eslint-disable-next-line react/no-unused-prop-types
|
// eslint-disable-next-line react/no-unused-prop-types
|
||||||
maxValue?: string;
|
maxValue?: string;
|
||||||
@@ -48,6 +54,13 @@ export type DatePickerAuxProps = PropsWithChildren &
|
|||||||
optionalClassName?: string;
|
optionalClassName?: string;
|
||||||
isRange?: boolean;
|
isRange?: boolean;
|
||||||
onClear: () => void;
|
onClear: () => void;
|
||||||
|
// For classic range mode: render labels above the wrapper
|
||||||
|
rangeLabels?: {
|
||||||
|
startLabel: string;
|
||||||
|
endLabel: string;
|
||||||
|
disabled?: boolean;
|
||||||
|
hideLabel?: boolean;
|
||||||
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -68,17 +81,21 @@ const DatePickerAux = ({
|
|||||||
disabled = false,
|
disabled = false,
|
||||||
optionalClassName,
|
optionalClassName,
|
||||||
isRange,
|
isRange,
|
||||||
|
rangeLabels,
|
||||||
ref,
|
ref,
|
||||||
...props
|
...props
|
||||||
}: DatePickerAuxProps) => {
|
}: DatePickerAuxProps) => {
|
||||||
const { t, currentLocale } = useCunningham();
|
const { t, currentLocale } = useCunningham();
|
||||||
const pickerRef = useRef<HTMLDivElement>(null);
|
const pickerRef = useRef<HTMLDivElement>(null);
|
||||||
|
const wrapperRef = useRef<HTMLDivElement>(null);
|
||||||
|
|
||||||
const isDateInvalid = useMemo(
|
const isDateInvalid = useMemo(
|
||||||
() => pickerState.validationState === "invalid" || props.state === "error",
|
() => pickerState.validationState === "invalid" || props.state === "error",
|
||||||
[pickerState.validationState, props.state],
|
[pickerState.validationState, props.state],
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const isClassic = props.variant === "classic";
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<I18nProvider locale={locale || currentLocale}>
|
<I18nProvider locale={locale || currentLocale}>
|
||||||
<Field
|
<Field
|
||||||
@@ -95,13 +112,91 @@ const DatePickerAux = ({
|
|||||||
"c__date-picker--success": props.state === "success",
|
"c__date-picker--success": props.state === "success",
|
||||||
"c__date-picker--focused":
|
"c__date-picker--focused":
|
||||||
!isDateInvalid && !disabled && (pickerState.isOpen || isFocused),
|
!isDateInvalid && !disabled && (pickerState.isOpen || isFocused),
|
||||||
|
"c__date-picker--classic": isClassic,
|
||||||
})}
|
})}
|
||||||
>
|
>
|
||||||
|
{isClassic && !isRange && (
|
||||||
|
<ClassicLabel
|
||||||
|
label={props.label}
|
||||||
|
hideLabel={props.hideLabel}
|
||||||
|
disabled={disabled}
|
||||||
|
className="c__date-picker__label"
|
||||||
|
disabledClassName="c__date-picker__label--disabled"
|
||||||
|
onClick={() => {
|
||||||
|
wrapperRef.current?.focus();
|
||||||
|
if (!pickerState.isOpen) {
|
||||||
|
pickerState.open();
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
{/* Classic variant: range labels above the wrapper */}
|
||||||
|
{isClassic && rangeLabels && !rangeLabels.hideLabel && (
|
||||||
|
<div className="c__date-picker__range__labels">
|
||||||
|
<ClassicLabel
|
||||||
|
label={rangeLabels.startLabel}
|
||||||
|
disabled={rangeLabels.disabled}
|
||||||
|
className="c__date-picker__label"
|
||||||
|
disabledClassName="c__date-picker__label--disabled"
|
||||||
|
onClick={() => {
|
||||||
|
wrapperRef.current?.focus();
|
||||||
|
if (!pickerState.isOpen) {
|
||||||
|
pickerState.open();
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<div className="c__date-picker__range__labels__spacer" />
|
||||||
|
<ClassicLabel
|
||||||
|
label={rangeLabels.endLabel}
|
||||||
|
disabled={rangeLabels.disabled}
|
||||||
|
className="c__date-picker__label"
|
||||||
|
disabledClassName="c__date-picker__label--disabled"
|
||||||
|
onClick={() => {
|
||||||
|
wrapperRef.current?.focus();
|
||||||
|
if (!pickerState.isOpen) {
|
||||||
|
pickerState.open();
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
{/* Hidden range labels for accessibility when hideLabel is true */}
|
||||||
|
{isClassic && rangeLabels && rangeLabels.hideLabel && (
|
||||||
|
<>
|
||||||
|
<ClassicLabel
|
||||||
|
label={rangeLabels.startLabel}
|
||||||
|
hideLabel
|
||||||
|
onClick={() => {
|
||||||
|
wrapperRef.current?.focus();
|
||||||
|
if (!pickerState.isOpen) {
|
||||||
|
pickerState.open();
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<ClassicLabel
|
||||||
|
label={rangeLabels.endLabel}
|
||||||
|
hideLabel
|
||||||
|
onClick={() => {
|
||||||
|
wrapperRef.current?.focus();
|
||||||
|
if (!pickerState.isOpen) {
|
||||||
|
pickerState.open();
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
<div
|
<div
|
||||||
className={classNames("c__date-picker__wrapper", {
|
className={classNames("c__date-picker__wrapper", {
|
||||||
"c__date-picker__wrapper--clickable": labelAsPlaceholder,
|
"c__date-picker__wrapper--clickable": labelAsPlaceholder,
|
||||||
})}
|
})}
|
||||||
ref={ref}
|
ref={(node) => {
|
||||||
|
wrapperRef.current = node;
|
||||||
|
if (typeof ref === "function") {
|
||||||
|
ref(node);
|
||||||
|
} else if (ref) {
|
||||||
|
ref.current = node;
|
||||||
|
}
|
||||||
|
}}
|
||||||
{...pickerProps.groupProps}
|
{...pickerProps.groupProps}
|
||||||
role="button"
|
role="button"
|
||||||
tabIndex={0}
|
tabIndex={0}
|
||||||
|
|||||||
@@ -1212,4 +1212,116 @@ describe("<DateRangePicker/>", () => {
|
|||||||
document.querySelector(".c__field.my-custom-class"),
|
document.querySelector(".c__field.my-custom-class"),
|
||||||
).toBeInTheDocument();
|
).toBeInTheDocument();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe("classic variant", () => {
|
||||||
|
it("renders with classic variant", async () => {
|
||||||
|
render(
|
||||||
|
<CunninghamProvider>
|
||||||
|
<DateRangePicker
|
||||||
|
label="Pick a date"
|
||||||
|
startLabel="Start date"
|
||||||
|
endLabel="End date"
|
||||||
|
variant="classic"
|
||||||
|
/>
|
||||||
|
</CunninghamProvider>,
|
||||||
|
);
|
||||||
|
// In classic mode, both labels are rendered in a row above the wrapper
|
||||||
|
expect(
|
||||||
|
document.querySelector(".c__date-picker--classic"),
|
||||||
|
).toBeInTheDocument();
|
||||||
|
expect(
|
||||||
|
document.querySelector(".c__date-picker__range__labels"),
|
||||||
|
).toBeInTheDocument();
|
||||||
|
// Should have two labels in the labels row
|
||||||
|
const labels = document.querySelectorAll(".c__date-picker__label");
|
||||||
|
expect(labels.length).toBe(2);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("labels are always static in classic variant", async () => {
|
||||||
|
const user = userEvent.setup();
|
||||||
|
render(
|
||||||
|
<CunninghamProvider>
|
||||||
|
<DateRangePicker
|
||||||
|
label="Pick a date"
|
||||||
|
startLabel="Start date"
|
||||||
|
endLabel="End date"
|
||||||
|
variant="classic"
|
||||||
|
/>
|
||||||
|
</CunninghamProvider>,
|
||||||
|
);
|
||||||
|
|
||||||
|
const startLabel = screen.getByText("Start date");
|
||||||
|
const endLabel = screen.getByText("End date");
|
||||||
|
|
||||||
|
// In classic variant, labels are outside the wrapper with c__date-picker__label class
|
||||||
|
expect(startLabel.classList.contains("c__date-picker__label")).toBe(true);
|
||||||
|
expect(endLabel.classList.contains("c__date-picker__label")).toBe(true);
|
||||||
|
|
||||||
|
// Open calendar
|
||||||
|
const toggleButton = (await screen.findAllByRole("button"))![1];
|
||||||
|
await user.click(toggleButton);
|
||||||
|
|
||||||
|
// Labels should still have the same class
|
||||||
|
expect(startLabel.classList.contains("c__date-picker__label")).toBe(true);
|
||||||
|
expect(endLabel.classList.contains("c__date-picker__label")).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("defaults to floating variant", () => {
|
||||||
|
render(
|
||||||
|
<CunninghamProvider>
|
||||||
|
<DateRangePicker
|
||||||
|
label="Pick a date"
|
||||||
|
startLabel="Start date"
|
||||||
|
endLabel="End date"
|
||||||
|
/>
|
||||||
|
</CunninghamProvider>,
|
||||||
|
);
|
||||||
|
expect(
|
||||||
|
document.querySelector(".c__date-picker--classic"),
|
||||||
|
).not.toBeInTheDocument();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("hideLabel", () => {
|
||||||
|
it("hides labels visually but keeps them accessible in floating variant", () => {
|
||||||
|
render(
|
||||||
|
<CunninghamProvider>
|
||||||
|
<DateRangePicker
|
||||||
|
label="Pick a date"
|
||||||
|
startLabel="Start date"
|
||||||
|
endLabel="End date"
|
||||||
|
hideLabel
|
||||||
|
/>
|
||||||
|
</CunninghamProvider>,
|
||||||
|
);
|
||||||
|
// Labels should be visually hidden via LabelledBox
|
||||||
|
const startLabel = screen.getByText("Start date");
|
||||||
|
const endLabel = screen.getByText("End date");
|
||||||
|
expect(startLabel.closest("label")).toHaveClass("c__offscreen");
|
||||||
|
expect(endLabel.closest("label")).toHaveClass("c__offscreen");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("hides labels visually but keeps them accessible in classic variant", () => {
|
||||||
|
render(
|
||||||
|
<CunninghamProvider>
|
||||||
|
<DateRangePicker
|
||||||
|
label="Pick a date"
|
||||||
|
startLabel="Start date"
|
||||||
|
endLabel="End date"
|
||||||
|
variant="classic"
|
||||||
|
hideLabel
|
||||||
|
/>
|
||||||
|
</CunninghamProvider>,
|
||||||
|
);
|
||||||
|
// Labels should be visually hidden with c__offscreen class
|
||||||
|
const startLabel = screen.getByText("Start date");
|
||||||
|
const endLabel = screen.getByText("End date");
|
||||||
|
expect(startLabel).toHaveClass("c__offscreen");
|
||||||
|
expect(endLabel).toHaveClass("c__offscreen");
|
||||||
|
// The visible labels row should not be present
|
||||||
|
expect(
|
||||||
|
document.querySelector(".c__date-picker__range__labels"),
|
||||||
|
).not.toBeInTheDocument();
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -60,6 +60,7 @@ export const DateRangePicker = ({
|
|||||||
const { startFieldProps, endFieldProps, calendarProps, ...pickerProps } =
|
const { startFieldProps, endFieldProps, calendarProps, ...pickerProps } =
|
||||||
useDateRangePicker(options, pickerState, ref);
|
useDateRangePicker(options, pickerState, ref);
|
||||||
|
|
||||||
|
const isClassic = props.variant === "classic";
|
||||||
const labelAsPlaceholder = useMemo(
|
const labelAsPlaceholder = useMemo(
|
||||||
() =>
|
() =>
|
||||||
!isFocused &&
|
!isFocused &&
|
||||||
@@ -88,14 +89,27 @@ export const DateRangePicker = ({
|
|||||||
});
|
});
|
||||||
},
|
},
|
||||||
calendar,
|
calendar,
|
||||||
|
// Pass labels for classic range mode
|
||||||
|
rangeLabels: isClassic
|
||||||
|
? {
|
||||||
|
startLabel,
|
||||||
|
endLabel,
|
||||||
|
disabled: props.disabled,
|
||||||
|
hideLabel: props.hideLabel,
|
||||||
|
}
|
||||||
|
: undefined,
|
||||||
}}
|
}}
|
||||||
ref={ref}
|
ref={ref}
|
||||||
>
|
>
|
||||||
<DateFieldBox
|
<DateFieldBox
|
||||||
{...{
|
{...{
|
||||||
...startFieldProps,
|
...startFieldProps,
|
||||||
label: startLabel,
|
// In classic mode, label is rendered outside by DatePickerAux
|
||||||
labelAsPlaceholder,
|
label: isClassic ? undefined : startLabel,
|
||||||
|
variant: props.variant,
|
||||||
|
hideLabel: isClassic ? undefined : props.hideLabel,
|
||||||
|
// In classic mode, always show date segments (never collapse them)
|
||||||
|
labelAsPlaceholder: isClassic ? false : labelAsPlaceholder,
|
||||||
onFocusChange: setIsFocused,
|
onFocusChange: setIsFocused,
|
||||||
disabled: props.disabled,
|
disabled: props.disabled,
|
||||||
}}
|
}}
|
||||||
@@ -104,8 +118,12 @@ export const DateRangePicker = ({
|
|||||||
<DateFieldBox
|
<DateFieldBox
|
||||||
{...{
|
{...{
|
||||||
...endFieldProps,
|
...endFieldProps,
|
||||||
label: endLabel,
|
// In classic mode, label is rendered outside by DatePickerAux
|
||||||
labelAsPlaceholder,
|
label: isClassic ? undefined : endLabel,
|
||||||
|
variant: props.variant,
|
||||||
|
hideLabel: isClassic ? undefined : props.hideLabel,
|
||||||
|
// In classic mode, always show date segments (never collapse them)
|
||||||
|
labelAsPlaceholder: isClassic ? false : labelAsPlaceholder,
|
||||||
onFocusChange: setIsFocused,
|
onFocusChange: setIsFocused,
|
||||||
disabled: props.disabled,
|
disabled: props.disabled,
|
||||||
}}
|
}}
|
||||||
|
|||||||
@@ -4,6 +4,33 @@
|
|||||||
.c__date-picker {
|
.c__date-picker {
|
||||||
position: relative;
|
position: relative;
|
||||||
|
|
||||||
|
&__label {
|
||||||
|
display: block;
|
||||||
|
font-size: var(--c--components--forms-labelledbox--classic-label-font-size);
|
||||||
|
color: var(--c--components--forms-labelledbox--label-color--small);
|
||||||
|
margin-bottom: var(--c--components--forms-labelledbox--classic-label-margin-bottom);
|
||||||
|
|
||||||
|
&--disabled {
|
||||||
|
color: var(--c--components--forms-labelledbox--label-color--small--disabled);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&--classic {
|
||||||
|
.c__date-picker__wrapper {
|
||||||
|
align-items: center;
|
||||||
|
height: 2.75rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.c__date-picker__inner__action {
|
||||||
|
margin-top: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.c__date-picker__range__separator {
|
||||||
|
align-self: center;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
&__wrapper {
|
&__wrapper {
|
||||||
border-radius: var(--c--components--forms-datepicker--border-radius);
|
border-radius: var(--c--components--forms-datepicker--border-radius);
|
||||||
border-width: var(--c--components--forms-datepicker--border-width);
|
border-width: var(--c--components--forms-datepicker--border-width);
|
||||||
@@ -202,6 +229,22 @@
|
|||||||
&__range {
|
&__range {
|
||||||
$component-min-width: px-to-rem(350px);
|
$component-min-width: px-to-rem(350px);
|
||||||
|
|
||||||
|
&__labels {
|
||||||
|
display: flex;
|
||||||
|
margin-bottom: var(--c--components--forms-labelledbox--classic-label-margin-bottom);
|
||||||
|
|
||||||
|
.c__date-picker__label {
|
||||||
|
flex: 1;
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__spacer {
|
||||||
|
// Space for the separator
|
||||||
|
width: 1rem;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// MUST READ:
|
// MUST READ:
|
||||||
// We can only use @container property for full-width fields, as the container-type: inline-size property
|
// We can only use @container property for full-width fields, as the container-type: inline-size property
|
||||||
// should not be based on the children's width. We cannot at the same time use container-type and a default
|
// should not be based on the children's width. We cannot at the same time use container-type and a default
|
||||||
|
|||||||
@@ -13,6 +13,19 @@ import { RhfDatePicker } from ":/components/Forms/DatePicker/stories-utils";
|
|||||||
export default {
|
export default {
|
||||||
title: "Components/Forms/DatePicker",
|
title: "Components/Forms/DatePicker",
|
||||||
component: DatePicker,
|
component: DatePicker,
|
||||||
|
argTypes: {
|
||||||
|
disabled: {
|
||||||
|
control: "boolean",
|
||||||
|
},
|
||||||
|
state: {
|
||||||
|
control: "select",
|
||||||
|
options: ["default", "success", "error"],
|
||||||
|
},
|
||||||
|
variant: {
|
||||||
|
control: "select",
|
||||||
|
options: ["floating", "classic"],
|
||||||
|
},
|
||||||
|
},
|
||||||
} as Meta<typeof DatePicker>;
|
} as Meta<typeof DatePicker>;
|
||||||
|
|
||||||
const Template: StoryFn<typeof DatePicker> = (args) => (
|
const Template: StoryFn<typeof DatePicker> = (args) => (
|
||||||
@@ -250,3 +263,80 @@ export const RangeControlledFull = () => {
|
|||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const ClassicVariant = () => (
|
||||||
|
<div style={{ minHeight: "400px" }}>
|
||||||
|
<DatePicker label="Pick a date" variant="classic" />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
|
||||||
|
export const ClassicVariantWithValue = () => (
|
||||||
|
<div style={{ minHeight: "400px" }}>
|
||||||
|
<DatePicker
|
||||||
|
label="Pick a date"
|
||||||
|
variant="classic"
|
||||||
|
defaultValue="2023-05-24T00:00:00.000+00:00"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
|
||||||
|
export const RangeClassicVariant = () => (
|
||||||
|
<div style={{ minHeight: "400px" }}>
|
||||||
|
<DateRangePicker
|
||||||
|
label="Pick a date range"
|
||||||
|
startLabel="Start date"
|
||||||
|
endLabel="End date"
|
||||||
|
variant="classic"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
|
||||||
|
export const RangeClassicVariantWithValue = () => (
|
||||||
|
<div style={{ minHeight: "400px" }}>
|
||||||
|
<DateRangePicker
|
||||||
|
label="Pick a date range"
|
||||||
|
startLabel="Start date"
|
||||||
|
endLabel="End date"
|
||||||
|
variant="classic"
|
||||||
|
defaultValue={[
|
||||||
|
"2023-05-23T00:00:00.000+00:00",
|
||||||
|
"2023-06-23T00:00:00.000+00:00",
|
||||||
|
]}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
|
||||||
|
export const HiddenLabel = () => (
|
||||||
|
<div style={{ minHeight: "400px" }}>
|
||||||
|
<DatePicker label="Pick a date" hideLabel />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
|
||||||
|
export const HiddenLabelClassic = () => (
|
||||||
|
<div style={{ minHeight: "400px" }}>
|
||||||
|
<DatePicker label="Pick a date" variant="classic" hideLabel />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
|
||||||
|
export const RangeHiddenLabel = () => (
|
||||||
|
<div style={{ minHeight: "400px" }}>
|
||||||
|
<DateRangePicker
|
||||||
|
label="Pick a date range"
|
||||||
|
startLabel="Start date"
|
||||||
|
endLabel="End date"
|
||||||
|
hideLabel
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
|
||||||
|
export const RangeHiddenLabelClassic = () => (
|
||||||
|
<div style={{ minHeight: "400px" }}>
|
||||||
|
<DateRangePicker
|
||||||
|
label="Pick a date range"
|
||||||
|
startLabel="Start date"
|
||||||
|
endLabel="End date"
|
||||||
|
variant="classic"
|
||||||
|
hideLabel
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
|||||||
Reference in New Issue
Block a user