Skip to content

feat(sdk-ui-filters): make date filter static period form accessible #6156

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 3 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions libs/sdk-ui-filters/api/sdk-ui-filters.api.md
Original file line number Diff line number Diff line change
Expand Up @@ -804,6 +804,8 @@ export interface IDateTranslator {

// @public
export interface IExtendedDateFilterErrors {
// (undocumented)
absoluteDateTimeForm?: IDateFilterAbsoluteDateTimeFormErrors;
absoluteForm?: IDateFilterAbsoluteFormErrors;
relativeForm?: IDateFilterRelativeFormErrors;
}
Expand Down
146 changes: 146 additions & 0 deletions libs/sdk-ui-filters/src/DateFilter/DateRangePicker/DatePicker.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
// (C) 2025 GoodData Corporation

import React, { forwardRef, useState } from "react";
import { IntlShape } from "react-intl";
import { WeekStart } from "@gooddata/sdk-model";
import { Overlay } from "@gooddata/sdk-ui-kit";
import {
DayPicker as DayPickerComponent,
DayPickerRangeProps,
SelectRangeEventHandler,
DayPickerProps,
} from "react-day-picker";
import enUS from "date-fns/locale/en-US/index.js";
import de from "date-fns/locale/de/index.js";
import es from "date-fns/locale/es/index.js";
import fr from "date-fns/locale/fr/index.js";
import ja from "date-fns/locale/ja/index.js";
import nl from "date-fns/locale/nl/index.js";
import pt from "date-fns/locale/pt/index.js";
import ptBR from "date-fns/locale/pt-BR/index.js";
import zhCN from "date-fns/locale/zh-CN/index.js";
import ru from "date-fns/locale/ru/index.js";
import it from "date-fns/locale/it/index.js";
import enGB from "date-fns/locale/en-GB/index.js";
import frCA from "date-fns/locale/fr-CA/index.js";
import enAU from "date-fns/locale/en-AU/index.js";
import fi from "date-fns/locale/fi/index.js";

import { mergeDayPickerProps } from "./utils.js";
import { IDateRange } from "./DateRangePicker.js";

const convertedLocales: Record<string, Locale> = {
"en-US": enUS,
"de-DE": de,
"es-ES": es,
"fr-FR": fr,
"ja-JP": ja,
"nl-NL": nl,
"pt-BR": ptBR,
"pt-PT": pt,
"zh-Hans": zhCN,
"ru-RU": ru,
"it-IT": it,
"es-419": es,
"en-GB": enGB,
"fr-CA": frCA,
"zh-Hant": zhCN,
"en-AU": enAU,
"fi-FI": fi,
"zh-HK": zhCN,
};

const ALIGN_POINTS = [{ align: "bl tl", offset: { x: 0, y: 1 } }];

const convertLocale = (locale: string): Locale => convertedLocales[locale];

function convertWeekStart(weekStart: WeekStart): DayPickerProps["weekStartsOn"] {
switch (weekStart) {
case "Sunday":
return 0;
case "Monday":
return 1;
default:
throw new Error(`Unknown week start ${weekStart}`);
}
}

export const DayPicker = forwardRef<
HTMLDivElement,
{
mode: "from" | "to";
originalDateRange: IDateRange;
selectedDateRange: IDateRange;
alignTo: string;
calendarClassNames: string;
onDateRangeSelect: SelectRangeEventHandler;
dayPickerProps?: DayPickerRangeProps;
weekStart?: WeekStart;
renderAsOverlay?: boolean;
intl: IntlShape;
}
>(
(
{
mode,
originalDateRange,
selectedDateRange,
onDateRangeSelect,
dayPickerProps,
alignTo,
weekStart,
renderAsOverlay,
calendarClassNames,
intl,
},
ref,
) => {
const [currentMonthDate, setCurrentMonthDate] = useState<Date | null>(
mode === "from" ? selectedDateRange.from : selectedDateRange.to,
);

const defaultDayPickerProps: DayPickerRangeProps = {
mode: "range",
showOutsideDays: true,
modifiers: { start: originalDateRange.from, end: originalDateRange.to },
selected: { from: originalDateRange.from, to: originalDateRange.to },
locale: convertLocale(intl.locale),
};

const dayPickerPropsWithDefaults = mergeDayPickerProps(defaultDayPickerProps, dayPickerProps);

const DatePicker = (
<div className="gd-date-range-picker-wrapper" ref={ref}>
<DayPickerComponent
{...dayPickerPropsWithDefaults}
month={currentMonthDate}
onSelect={onDateRangeSelect}
selected={selectedDateRange}
classNames={{
root: calendarClassNames,
}}
onMonthChange={setCurrentMonthDate}
weekStartsOn={convertWeekStart(weekStart)}
/>
</div>
);

const OverlayDatePicker = (
<Overlay
alignTo={alignTo}
alignPoints={ALIGN_POINTS}
closeOnOutsideClick={true}
closeOnMouseDrag={true}
closeOnParentScroll={true}
>
{DatePicker}
</Overlay>
);
if (renderAsOverlay) {
return OverlayDatePicker;
}
return DatePicker;
},
);

DayPicker.displayName = "DayPicker";
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
// (C) 2025 GoodData Corporation

import React from "react";
import { IntlShape } from "react-intl";

import { getLocalizedDateFormat } from "../utils/FormattingUtils.js";

import { DATE_INPUT_HINT_ID, TIME_INPUT_HINT_ID } from "./types.js";

export const DateRangeHint: React.FC<{
dateFormat: string;
isTimeEnabled: boolean;
intl: IntlShape;
}> = ({ dateFormat, isTimeEnabled, intl }) => (
<div className="gd-date-range__hint">
<div id={DATE_INPUT_HINT_ID}>
{intl.formatMessage(
{ id: "filters.staticPeriod.dateFormatHint" },
{ format: dateFormat || getLocalizedDateFormat(intl.locale) },
)}
</div>
{isTimeEnabled ? (
<div id={TIME_INPUT_HINT_ID}>
{intl.formatMessage({ id: "filters.staticPeriod.timeFormatHint" })}
</div>
) : null}
</div>
);
Loading
Loading