diff --git a/superset-frontend/src/explore/components/controls/DateFilterControl/DateFilterLabel.tsx b/superset-frontend/src/explore/components/controls/DateFilterControl/DateFilterLabel.tsx index c68b436a0e497..ce4c32191d0d2 100644 --- a/superset-frontend/src/explore/components/controls/DateFilterControl/DateFilterLabel.tsx +++ b/superset-frontend/src/explore/components/controls/DateFilterControl/DateFilterLabel.tsx @@ -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 ? ( +
+ {label && {label}} + {range && ( +
css` + margin-top: ${theme.gridUnit}px; + `} + > + {range} +
+ )} +
+ ) : ( + 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(false); const [evalResponse, setEvalResponse] = useState(value); - const [tooltipTitle, setTooltipTitle] = useState(value); + const [tooltipTitle, setTooltipTitle] = useState(value); const theme = useTheme(); + const [labelRef, labelIsTruncated] = useCSSTextTruncation(); 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' }} > - + ref={labelRef} + /> ); @@ -335,13 +372,14 @@ export default function DateFilterLabel(props: DateFilterControlProps) { const modalContent = ( <> - + ref={labelRef} + /> {/* the zIndex value is from trying so that the Modal doesn't overlay the AdhocFilter when GENERIC_CHART_AXES is enabled */} 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) => { + const theme = useTheme(); + return ( + + + {props.label} + + + + ); + }, +); diff --git a/superset-frontend/src/explore/components/controls/DateFilterControl/components/index.ts b/superset-frontend/src/explore/components/controls/DateFilterControl/components/index.ts index 0bec821065627..0d46ee7a97d19 100644 --- a/superset-frontend/src/explore/components/controls/DateFilterControl/components/index.ts +++ b/superset-frontend/src/explore/components/controls/DateFilterControl/components/index.ts @@ -20,3 +20,4 @@ export { CommonFrame } from './CommonFrame'; export { CalendarFrame } from './CalendarFrame'; export { CustomFrame } from './CustomFrame'; export { AdvancedFrame } from './AdvancedFrame'; +export { DateLabel } from './DateLabel'; diff --git a/superset-frontend/src/filters/components/Time/TimeFilterPlugin.tsx b/superset-frontend/src/filters/components/Time/TimeFilterPlugin.tsx index 0d4185b7592e6..0e0678d890dbf 100644 --- a/superset-frontend/src/filters/components/Time/TimeFilterPlugin.tsx +++ b/superset-frontend/src/filters/components/Time/TimeFilterPlugin.tsx @@ -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}`} } `; @@ -99,7 +82,6 @@ export default function TimeFilterPlugin(props: PluginFilterTimeProps) { return props.formData?.inView ? (