feat: New time range label (#22317)
This commit is contained in:
parent
aafb993ee2
commit
2d30e9cbe9
|
|
@ -16,11 +16,17 @@
|
|||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
import React, { useState, useEffect, useMemo } from 'react';
|
||||
import { css, styled, t, useTheme, NO_TIME_RANGE } from '@superset-ui/core';
|
||||
import React, { ReactNode, useState, useEffect, useMemo } from 'react';
|
||||
import {
|
||||
css,
|
||||
styled,
|
||||
t,
|
||||
useTheme,
|
||||
NO_TIME_RANGE,
|
||||
SupersetTheme,
|
||||
} from '@superset-ui/core';
|
||||
import Button from 'src/components/Button';
|
||||
import ControlHeader from 'src/explore/components/ControlHeader';
|
||||
import Label from 'src/components/Label';
|
||||
import Modal from 'src/components/Modal';
|
||||
import { Divider } from 'src/components';
|
||||
import Icons from 'src/components/Icons';
|
||||
|
|
@ -29,6 +35,7 @@ import { Tooltip } from 'src/components/Tooltip';
|
|||
import { useDebouncedEffect } from 'src/explore/exploreUtils';
|
||||
import { SLOW_DEBOUNCE } from 'src/constants';
|
||||
import { noOp } from 'src/utils/common';
|
||||
import { useCSSTextTruncation } from 'src/hooks/useTruncation';
|
||||
import ControlPopover from '../ControlPopover/ControlPopover';
|
||||
|
||||
import { DateFilterControlProps, FrameType } from './types';
|
||||
|
|
@ -44,6 +51,7 @@ import {
|
|||
CalendarFrame,
|
||||
CustomFrame,
|
||||
AdvancedFrame,
|
||||
DateLabel,
|
||||
} from './components';
|
||||
|
||||
const StyledRangeType = styled(Select)`
|
||||
|
|
@ -120,6 +128,28 @@ const IconWrapper = styled.span`
|
|||
}
|
||||
`;
|
||||
|
||||
const getTooltipTitle = (
|
||||
isLabelTruncated: boolean,
|
||||
label: string | undefined,
|
||||
range: string | undefined,
|
||||
) =>
|
||||
isLabelTruncated ? (
|
||||
<div>
|
||||
{label && <strong>{label}</strong>}
|
||||
{range && (
|
||||
<div
|
||||
css={(theme: SupersetTheme) => css`
|
||||
margin-top: ${theme.gridUnit}px;
|
||||
`}
|
||||
>
|
||||
{range}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
) : (
|
||||
range || null
|
||||
);
|
||||
|
||||
export default function DateFilterLabel(props: DateFilterControlProps) {
|
||||
const {
|
||||
onChange,
|
||||
|
|
@ -139,13 +169,14 @@ export default function DateFilterLabel(props: DateFilterControlProps) {
|
|||
const [timeRangeValue, setTimeRangeValue] = useState(value);
|
||||
const [validTimeRange, setValidTimeRange] = useState<boolean>(false);
|
||||
const [evalResponse, setEvalResponse] = useState<string>(value);
|
||||
const [tooltipTitle, setTooltipTitle] = useState<string>(value);
|
||||
const [tooltipTitle, setTooltipTitle] = useState<ReactNode | null>(value);
|
||||
const theme = useTheme();
|
||||
const [labelRef, labelIsTruncated] = useCSSTextTruncation<HTMLSpanElement>();
|
||||
|
||||
useEffect(() => {
|
||||
if (value === NO_TIME_RANGE) {
|
||||
setActualTimeRange(NO_TIME_RANGE);
|
||||
setTooltipTitle(NO_TIME_RANGE);
|
||||
setTooltipTitle(null);
|
||||
setValidTimeRange(true);
|
||||
return;
|
||||
}
|
||||
|
|
@ -153,7 +184,7 @@ export default function DateFilterLabel(props: DateFilterControlProps) {
|
|||
if (error) {
|
||||
setEvalResponse(error || '');
|
||||
setValidTimeRange(false);
|
||||
setTooltipTitle(value || '');
|
||||
setTooltipTitle(value || null);
|
||||
} else {
|
||||
/*
|
||||
HRT == human readable text
|
||||
|
|
@ -172,16 +203,21 @@ export default function DateFilterLabel(props: DateFilterControlProps) {
|
|||
guessedFrame === 'No filter'
|
||||
) {
|
||||
setActualTimeRange(value);
|
||||
setTooltipTitle(
|
||||
getTooltipTitle(labelIsTruncated, value, actualRange),
|
||||
);
|
||||
} else {
|
||||
setActualTimeRange(actualRange || '');
|
||||
setTooltipTitle(value || '');
|
||||
setTooltipTitle(
|
||||
getTooltipTitle(labelIsTruncated, actualRange, value),
|
||||
);
|
||||
}
|
||||
setValidTimeRange(true);
|
||||
}
|
||||
setLastFetchedTimeRange(value);
|
||||
setEvalResponse(actualRange || value);
|
||||
});
|
||||
}, [value]);
|
||||
}, [guessedFrame, labelIsTruncated, labelRef, value]);
|
||||
|
||||
useDebouncedEffect(
|
||||
() => {
|
||||
|
|
@ -322,12 +358,13 @@ export default function DateFilterLabel(props: DateFilterControlProps) {
|
|||
overlayStyle={{ width: '600px' }}
|
||||
>
|
||||
<Tooltip placement="top" title={tooltipTitle}>
|
||||
<Label
|
||||
className="pointer"
|
||||
<DateLabel
|
||||
label={actualTimeRange}
|
||||
isActive={show}
|
||||
isPlaceholder={actualTimeRange === NO_TIME_RANGE}
|
||||
data-test={DATE_FILTER_TEST_KEY.popoverOverlay}
|
||||
>
|
||||
{actualTimeRange}
|
||||
</Label>
|
||||
ref={labelRef}
|
||||
/>
|
||||
</Tooltip>
|
||||
</ControlPopover>
|
||||
);
|
||||
|
|
@ -335,13 +372,14 @@ export default function DateFilterLabel(props: DateFilterControlProps) {
|
|||
const modalContent = (
|
||||
<>
|
||||
<Tooltip placement="top" title={tooltipTitle}>
|
||||
<Label
|
||||
className="pointer"
|
||||
<DateLabel
|
||||
onClick={toggleOverlay}
|
||||
label={actualTimeRange}
|
||||
isActive={show}
|
||||
isPlaceholder={actualTimeRange === NO_TIME_RANGE}
|
||||
data-test={DATE_FILTER_TEST_KEY.modalOverlay}
|
||||
>
|
||||
{actualTimeRange}
|
||||
</Label>
|
||||
ref={labelRef}
|
||||
/>
|
||||
</Tooltip>
|
||||
{/* the zIndex value is from trying so that the Modal doesn't overlay the AdhocFilter when GENERIC_CHART_AXES is enabled */}
|
||||
<Modal
|
||||
|
|
|
|||
|
|
@ -0,0 +1,100 @@
|
|||
/**
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
import React, { forwardRef, ReactNode, RefObject } from 'react';
|
||||
import { css, styled, useTheme } from '@superset-ui/core';
|
||||
import Icons from 'src/components/Icons';
|
||||
|
||||
export type DateLabelProps = {
|
||||
label: ReactNode;
|
||||
isActive?: boolean;
|
||||
isPlaceholder?: boolean;
|
||||
onClick?: (event: React.MouseEvent) => void;
|
||||
};
|
||||
|
||||
// This is the color that antd components (such as Select or Input) use on hover
|
||||
// TODO: use theme.colors.primary.base here and in antd components
|
||||
const ACTIVE_BORDER_COLOR = '#45BED6';
|
||||
|
||||
const LabelContainer = styled.div<{
|
||||
isActive?: boolean;
|
||||
isPlaceholder?: boolean;
|
||||
}>`
|
||||
${({ theme, isActive, isPlaceholder }) => css`
|
||||
width: 100%;
|
||||
height: ${theme.gridUnit * 8}px;
|
||||
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex-wrap: nowrap;
|
||||
|
||||
padding: 0 ${theme.gridUnit * 3}px;
|
||||
|
||||
background-color: ${theme.colors.grayscale.light5};
|
||||
|
||||
border: 1px solid
|
||||
${isActive ? ACTIVE_BORDER_COLOR : theme.colors.grayscale.light2};
|
||||
border-radius: ${theme.borderRadius}px;
|
||||
|
||||
cursor: pointer;
|
||||
|
||||
transition: border-color 0.3s cubic-bezier(0.65, 0.05, 0.36, 1);
|
||||
:hover,
|
||||
:focus {
|
||||
border-color: ${ACTIVE_BORDER_COLOR};
|
||||
}
|
||||
|
||||
.date-label-content {
|
||||
color: ${isPlaceholder
|
||||
? theme.colors.grayscale.light1
|
||||
: theme.colors.grayscale.dark1};
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
min-width: 0;
|
||||
flex-shrink: 1;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
span[role='img'] {
|
||||
margin-left: auto;
|
||||
padding-left: ${theme.gridUnit}px;
|
||||
|
||||
& > span[role='img'] {
|
||||
line-height: 0;
|
||||
}
|
||||
}
|
||||
`}
|
||||
`;
|
||||
|
||||
export const DateLabel = forwardRef(
|
||||
(props: DateLabelProps, ref: RefObject<HTMLSpanElement>) => {
|
||||
const theme = useTheme();
|
||||
return (
|
||||
<LabelContainer {...props} tabIndex={0}>
|
||||
<span className="date-label-content" ref={ref}>
|
||||
{props.label}
|
||||
</span>
|
||||
<Icons.CalendarOutlined
|
||||
iconSize="s"
|
||||
iconColor={theme.colors.grayscale.base}
|
||||
/>
|
||||
</LabelContainer>
|
||||
);
|
||||
},
|
||||
);
|
||||
|
|
@ -20,3 +20,4 @@ export { CommonFrame } from './CommonFrame';
|
|||
export { CalendarFrame } from './CalendarFrame';
|
||||
export { CustomFrame } from './CustomFrame';
|
||||
export { AdvancedFrame } from './AdvancedFrame';
|
||||
export { DateLabel } from './DateLabel';
|
||||
|
|
|
|||
|
|
@ -38,28 +38,11 @@ const ControlContainer = styled.div<{
|
|||
display: flex;
|
||||
height: 100%;
|
||||
max-width: 100%;
|
||||
padding: 2px;
|
||||
& > span,
|
||||
& > span:hover {
|
||||
border: 2px solid transparent;
|
||||
display: inline-block;
|
||||
border: ${({ theme, validateStatus }) =>
|
||||
validateStatus && `2px solid ${theme.colors[validateStatus]?.base}`};
|
||||
}
|
||||
&:focus {
|
||||
& > span {
|
||||
border: 2px solid
|
||||
${({ theme, validateStatus }) =>
|
||||
validateStatus
|
||||
? theme.colors[validateStatus]?.base
|
||||
: theme.colors.primary.base};
|
||||
outline: 0;
|
||||
box-shadow: 0 0 0 2px
|
||||
${({ validateStatus }) =>
|
||||
validateStatus
|
||||
? 'rgba(224, 67, 85, 12%)'
|
||||
: 'rgba(32, 167, 201, 0.2)'};
|
||||
}
|
||||
width: 100%;
|
||||
& > div,
|
||||
& > div:hover {
|
||||
${({ validateStatus, theme }) =>
|
||||
validateStatus && `border-color: ${theme.colors[validateStatus]?.base}`}
|
||||
}
|
||||
`;
|
||||
|
||||
|
|
@ -101,7 +84,6 @@ export default function TimeFilterPlugin(props: PluginFilterTimeProps) {
|
|||
return props.formData?.inView ? (
|
||||
<TimeFilterStyles width={width} height={height}>
|
||||
<ControlContainer
|
||||
tabIndex={-1}
|
||||
ref={inputRef}
|
||||
validateStatus={filterState.validateStatus}
|
||||
onFocus={setFocusedFilter}
|
||||
|
|
|
|||
Loading…
Reference in New Issue