Skip to content

Commit 299b0c7

Browse files
committed
fix(ui): replace mui date picker with core picker
1 parent 805649a commit 299b0c7

11 files changed

Lines changed: 543 additions & 1008 deletions

File tree

openmetadata-ui-core-components/src/main/resources/ui/src/components/application/date-picker/date-range-picker.tsx

Lines changed: 109 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { useMemo, useState } from 'react';
1+
import { useMemo, useState, type ReactNode } from 'react';
22
import {
33
endOfMonth,
44
endOfWeek,
@@ -8,7 +8,7 @@ import {
88
today,
99
} from '@internationalized/date';
1010
import { useControlledState } from '@react-stately/utils';
11-
import { Calendar as CalendarIcon } from '@untitledui/icons';
11+
import { Calendar as CalendarIcon, XClose } from '@untitledui/icons';
1212
import { useDateFormatter } from 'react-aria';
1313
import type {
1414
DateRangePickerProps as AriaDateRangePickerProps,
@@ -22,7 +22,9 @@ import {
2222
useLocale,
2323
} from 'react-aria-components';
2424
import { Button } from '@/components/base/buttons/button';
25+
import type { ButtonProps } from '@/components/base/buttons/button';
2526
import { cx } from '@/utils/cx';
27+
import type { RangeValue } from '@react-types/shared';
2628
import { DateInput } from './date-input';
2729
import { RangeCalendar } from './range-calendar';
2830
import { RangePresetButton } from './range-preset';
@@ -31,19 +33,55 @@ const now = today(getLocalTimeZone());
3133

3234
const highlightedDates = [today(getLocalTimeZone())];
3335

36+
export type DateRangePickerValue = RangeValue<DateValue>;
37+
38+
export interface DateRangePickerPreset {
39+
key: string;
40+
label: ReactNode;
41+
value: DateRangePickerValue;
42+
}
43+
44+
type DateRangePickerButtonProps = ButtonProps & {
45+
[key: `data-${string}`]: string | undefined;
46+
};
47+
3448
interface DateRangePickerProps extends AriaDateRangePickerProps<DateValue> {
3549
/** The function to call when the apply button is clicked. */
36-
onApply?: () => void;
50+
onApply?: (value: DateRangePickerValue | null, presetKey?: string) => void;
3751
/** The function to call when the cancel button is clicked. */
3852
onCancel?: () => void;
53+
/** Preset ranges to show in the side rail. */
54+
presets?: DateRangePickerPreset[];
55+
/** Label to render in the trigger button instead of the formatted date range. */
56+
triggerLabel?: ReactNode;
57+
/** Placeholder to render when there is no selected date range. */
58+
placeholder?: ReactNode;
59+
/** Props passed to the trigger button. */
60+
buttonProps?: DateRangePickerButtonProps;
61+
/** Show an inline clear action in the trigger button when a range is selected. */
62+
allowClear?: boolean;
63+
/** The function to call when the clear action is clicked. */
64+
onClear?: () => void;
65+
/** Test id for the clear action. */
66+
clearButtonTestId?: string;
67+
/** Applies and closes the picker immediately when a preset is selected. */
68+
applyOnPresetSelect?: boolean;
3969
}
4070

4171
export const DateRangePicker = ({
72+
allowClear,
73+
applyOnPresetSelect,
74+
buttonProps,
75+
clearButtonTestId = 'clear-date-picker',
76+
onClear,
4277
value: valueProp,
4378
defaultValue,
4479
onChange,
4580
onApply,
4681
onCancel,
82+
placeholder = 'Select dates',
83+
presets: presetsProp,
84+
triggerLabel,
4785
...props
4886
}: DateRangePickerProps) => {
4987
const { locale } = useLocale();
@@ -66,7 +104,7 @@ export const DateRangePicker = ({
66104
? formatter.format(value.end.toDate(getLocalTimeZone()))
67105
: 'Select date';
68106

69-
const presets = useMemo(
107+
const defaultPresets = useMemo(
70108
() => ({
71109
today: { label: 'Today', value: { start: now, end: now } },
72110
yesterday: {
@@ -122,6 +160,40 @@ export const DateRangePicker = ({
122160
}),
123161
[locale]
124162
);
163+
const presets = useMemo(
164+
() =>
165+
presetsProp ??
166+
Object.entries(defaultPresets).map(([key, preset]) => ({
167+
key,
168+
label: preset.label,
169+
value: preset.value,
170+
})),
171+
[defaultPresets, presetsProp]
172+
);
173+
const calendarPresets = useMemo(
174+
() =>
175+
Object.fromEntries(
176+
presets
177+
.slice(0, 3)
178+
.map((preset) => [
179+
preset.key,
180+
{ label: preset.label, value: preset.value },
181+
])
182+
),
183+
[presets]
184+
);
185+
186+
const handleApply = (close: () => void, presetKey?: string) => {
187+
onApply?.(value, presetKey);
188+
close();
189+
};
190+
const triggerContent =
191+
triggerLabel ??
192+
(value ? (
193+
`${formattedStartDate}${formattedEndDate}`
194+
) : (
195+
<span className="tw:text-placeholder">{placeholder}</span>
196+
));
125197

126198
return (
127199
<AriaDateRangePicker
@@ -131,11 +203,32 @@ export const DateRangePicker = ({
131203
value={value}
132204
onChange={setValue}>
133205
<AriaGroup>
134-
<Button color="secondary" iconLeading={CalendarIcon} size="md">
135-
{!value ? (
136-
<span className="tw:text-placeholder">Select dates</span>
137-
) : (
138-
`${formattedStartDate}${formattedEndDate}`
206+
<Button
207+
color="secondary"
208+
iconLeading={CalendarIcon}
209+
size="md"
210+
{...buttonProps}>
211+
{triggerContent}
212+
{allowClear && value && (
213+
<span
214+
data-testid={clearButtonTestId}
215+
role="button"
216+
tabIndex={0}
217+
onClick={(event) => {
218+
event.stopPropagation();
219+
setValue(null);
220+
onClear?.();
221+
}}
222+
onKeyDown={(event) => {
223+
if (event.key === 'Enter' || event.key === ' ') {
224+
event.preventDefault();
225+
event.stopPropagation();
226+
setValue(null);
227+
onClear?.();
228+
}
229+
}}>
230+
<XClose className="tw:size-4" />
231+
</span>
139232
)}
140233
</Button>
141234
</AriaGroup>
@@ -157,11 +250,15 @@ export const DateRangePicker = ({
157250
<div className="tw:hidden tw:w-38 tw:flex-col tw:gap-0.5 tw:border-r tw:border-solid tw:border-secondary tw:p-3 tw:lg:flex">
158251
{Object.values(presets).map((preset) => (
159252
<RangePresetButton
160-
key={preset.label}
253+
key={preset.key}
161254
value={preset.value}
162255
onClick={() => {
163256
setValue(preset.value);
164257
setFocusedValue(preset.value.start);
258+
if (applyOnPresetSelect) {
259+
onApply?.(preset.value, preset.key);
260+
close();
261+
}
165262
}}>
166263
{preset.label}
167264
</RangePresetButton>
@@ -171,11 +268,7 @@ export const DateRangePicker = ({
171268
<RangeCalendar
172269
focusedValue={focusedValue}
173270
highlightedDates={highlightedDates}
174-
presets={{
175-
lastWeek: presets.lastWeek,
176-
lastMonth: presets.lastMonth,
177-
lastYear: presets.lastYear,
178-
}}
271+
presets={calendarPresets}
179272
onFocusChange={setFocusedValue}
180273
/>
181274
<div className="tw:flex tw:justify-between tw:gap-3 tw:border-t tw:border-secondary tw:p-4">
@@ -197,10 +290,7 @@ export const DateRangePicker = ({
197290
<Button
198291
color="primary"
199292
size="md"
200-
onClick={() => {
201-
onApply?.();
202-
close();
203-
}}>
293+
onClick={() => handleApply(close)}>
204294
Apply
205295
</Button>
206296
</div>

openmetadata-ui-core-components/src/main/resources/ui/src/components/application/date-picker/range-calendar.tsx

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import type { HTMLAttributes, PropsWithChildren } from 'react';
1+
import type { HTMLAttributes, PropsWithChildren, ReactNode } from 'react';
22
import { Fragment, useContext, useState } from 'react';
33
import type { CalendarDate } from '@internationalized/date';
44
import { ChevronLeft, ChevronRight } from '@untitledui/icons';
@@ -93,7 +93,7 @@ interface RangeCalendarProps extends AriaRangeCalendarProps<DateValue> {
9393
/** The date presets to display. */
9494
presets?: Record<
9595
string,
96-
{ label: string; value: { start: DateValue; end: DateValue } }
96+
{ label: ReactNode; value: { start: DateValue; end: DateValue } }
9797
>;
9898
}
9999

@@ -144,8 +144,8 @@ export const RangeCalendar = ({ presets, ...props }: RangeCalendarProps) => {
144144

145145
{!isDesktop && presets && (
146146
<div className="tw:mt-1 tw:flex tw:justify-between tw:gap-3 tw:px-2 tw:md:hidden">
147-
{Object.values(presets).map((preset) => (
148-
<MobilePresetButton key={preset.label} value={preset.value}>
147+
{Object.entries(presets).map(([key, preset]) => (
148+
<MobilePresetButton key={key} value={preset.value}>
149149
{preset.label}
150150
</MobilePresetButton>
151151
))}

openmetadata-ui/src/main/resources/ui/src/components/DataQuality/IncidentManager/DimensionalityTab/DimensionalityTab.tsx

Lines changed: 76 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -10,16 +10,23 @@
1010
* See the License for the specific language governing permissions and
1111
* limitations under the License.
1212
*/
13-
import { Select, Skeleton, Table } from '@openmetadata/ui-core-components';
13+
import {
14+
DateRangePicker,
15+
Select,
16+
Skeleton,
17+
Table,
18+
} from '@openmetadata/ui-core-components';
1419
import { format } from 'date-fns';
15-
import { isEmpty, split, toLower } from 'lodash';
20+
import { isEmpty, isUndefined, split, toLower } from 'lodash';
1621
import { DateRangeObject } from 'Models';
22+
import type { ComponentType, ReactElement } from 'react';
1723
import { useCallback, useEffect, useMemo, useState } from 'react';
18-
import { Trans, useTranslation } from 'react-i18next';
24+
import { Trans as ReactI18nextTrans, useTranslation } from 'react-i18next';
1925
import { Link } from 'react-router-dom';
2026
import {
2127
DEFAULT_RANGE_DATA,
2228
DEFAULT_SELECTED_RANGE,
29+
PROFILER_FILTER_RANGE,
2330
TEST_CASE_STATUS_LABELS,
2431
} from '../../../../constants/profiler.constant';
2532
import { SIZE } from '../../../../enums/common.enum';
@@ -30,25 +37,58 @@ import {
3037
getEndOfDayInMillis,
3138
getStartOfDayInMillis,
3239
} from '../../../../utils/date-time/DateTimeUtils';
40+
import {
41+
getCoreDateRangeValue,
42+
getDateRangeObjectFromCorePicker,
43+
getDateRangePickerPresets,
44+
} from '../../../../utils/DatePickerMenuUtils';
3345
import { getEntityFQN } from '../../../../utils/FeedUtilsPure';
46+
import { translateWithNestedKeys } from '../../../../utils/i18next/LocalUtil';
3447
import {
3548
getEntityDetailsPath,
3649
getTestCaseDimensionsDetailPagePath,
3750
} from '../../../../utils/RouterUtils';
3851
import { useRequiredParams } from '../../../../utils/useRequiredParams';
3952
import DateTimeDisplay from '../../../common/DateTimeDisplay/DateTimeDisplay';
4053
import NoDataPlaceholderNew from '../../../common/ErrorWithPlaceholder/NoDataPlaceholderNew';
41-
import MuiDatePickerMenu from '../../../common/MuiDatePickerMenu/MuiDatePickerMenu';
4254
import StatusBadge from '../../../common/StatusBadge/StatusBadge.component';
4355
import { StatusType } from '../../../common/StatusBadge/StatusBadge.interface';
4456
import { ProfilerTabPath } from '../../../Database/Profiler/ProfilerDashboard/profilerDashboard.interface';
4557
import DimensionalityHeatmap from './DimensionalityHeatmap/DimensionalityHeatmap.component';
4658
import { DimensionResultWithTimestamp } from './DimensionalityHeatmap/DimensionalityHeatmap.interface';
59+
60+
const TransWithComponents = ReactI18nextTrans as unknown as ComponentType<{
61+
components: Record<number, ReactElement>;
62+
i18nKey: string;
63+
}>;
64+
4765
const DimensionalityTab = () => {
4866
const { t } = useTranslation();
4967
const { dimensionKey } = useRequiredParams<{ dimensionKey?: string }>();
5068
const { testCase } = useTestCaseStore();
5169
const [dateRange, setDateRange] = useState(DEFAULT_RANGE_DATA);
70+
const menuOptions = useMemo(() => {
71+
return Object.fromEntries(
72+
Object.entries(PROFILER_FILTER_RANGE).map(([key, value]) => [
73+
key,
74+
{
75+
...value,
76+
title: translateWithNestedKeys(value.title, value.titleData),
77+
},
78+
])
79+
);
80+
}, [t]);
81+
const [selectedDateRangeLabel, setSelectedDateRangeLabel] = useState(
82+
menuOptions[DEFAULT_SELECTED_RANGE.key].title
83+
);
84+
const dateRangePickerValue = useMemo(
85+
() => getCoreDateRangeValue(dateRange),
86+
[dateRange]
87+
);
88+
const dateRangePickerPresets = useMemo(
89+
() => getDateRangePickerPresets(menuOptions),
90+
[menuOptions]
91+
);
5292
const [dimensionData, setDimensionData] = useState<
5393
DimensionResultWithTimestamp[]
5494
>([]);
@@ -91,6 +131,27 @@ const DimensionalityTab = () => {
91131
});
92132
};
93133

134+
const handleDateRangeApply = (
135+
value:
136+
| Parameters<typeof getDateRangeObjectFromCorePicker>[0]['value']
137+
| null,
138+
presetKey?: string
139+
) => {
140+
if (isUndefined(value) || value === null) {
141+
return;
142+
}
143+
144+
const { range } = getDateRangeObjectFromCorePicker({
145+
menuOptions,
146+
presetKey,
147+
showSelectedCustomRange: true,
148+
value,
149+
});
150+
151+
setSelectedDateRangeLabel(range.title ?? selectedDateRangeLabel);
152+
handleDateRangeChange(range);
153+
};
154+
94155
const handleDimensionChange = (value: string | number | null) => {
95156
if (!value) {
96157
return;
@@ -244,7 +305,7 @@ const DimensionalityTab = () => {
244305

245306
return (
246307
<NoDataPlaceholderNew size={SIZE.LARGE}>
247-
<Trans
308+
<TransWithComponents
248309
components={{
249310
0: <span className="tw:text-sm" />,
250311
1: <span className="tw:text-sm" />,
@@ -280,11 +341,16 @@ const DimensionalityTab = () => {
280341
<p className="tw:m-0 tw:text-sm tw:font-medium tw:whitespace-nowrap tw:text-primary">
281342
{`${t('label.date')}:`}
282343
</p>
283-
<MuiDatePickerMenu
284-
showSelectedCustomRange
285-
defaultDateRange={DEFAULT_SELECTED_RANGE}
286-
handleDateRangeChange={handleDateRangeChange}
287-
size="small"
344+
<DateRangePicker
345+
applyOnPresetSelect
346+
buttonProps={{
347+
'data-testid': 'date-range-picker',
348+
size: 'sm',
349+
}}
350+
presets={dateRangePickerPresets}
351+
triggerLabel={selectedDateRangeLabel}
352+
value={dateRangePickerValue}
353+
onApply={handleDateRangeApply}
288354
/>
289355
</div>
290356
</div>

0 commit comments

Comments
 (0)