diff --git a/assets/stylesheets/App.css b/assets/stylesheets/App.css index abaac781..d481af51 100644 --- a/assets/stylesheets/App.css +++ b/assets/stylesheets/App.css @@ -47,6 +47,10 @@ font-family: 'DM Sans'; } +.ant-dropdown { + z-index: 999; +} + .ant-typography code, .ant-typography pre, .ͼ1 .cm-scroller { @@ -793,7 +797,7 @@ body { } .input-number-cal .ant-input { - font-family: 'DM Mono'; + font-family: 'DM Sans'; font-weight: 400; } diff --git a/src/components/Dashboard/common/FilterInput.jsx b/src/components/Dashboard/common/FilterInput.jsx index eebc5915..7aee482d 100644 --- a/src/components/Dashboard/common/FilterInput.jsx +++ b/src/components/Dashboard/common/FilterInput.jsx @@ -17,6 +17,7 @@ import { useCompactItemContext } from 'antd/es/space/Compact' import useStyle, { useSharedStyle } from 'antd/es/input/style' import { useThemeContext } from '../context/ThemeContext' import SimplePropertyFilter from './SimplePropertyFilter' +import QuickPropertyFilters from './QuickPropertyFilters' import ScrollBox from './ScrollBox' import ChevronRightIcon from '../../Icons/ChevronRightIcon' import GreaterThanIcon from '../../Icons/GreaterThanIcon' @@ -25,9 +26,12 @@ import LessThanIcon from '../../Icons/LessThanIcon' import LessThanOrEqualToIcon from '../../Icons/LessThanOrEqualToIcon' import NotEqualIcon from '../../Icons/NotEqualIcon' import EqualIcon from '../../Icons/EqualIcon' +import { Divider } from 'antd' const operandIconStyle = { fontSize: 8 } +const PROPERTY_FILTER_PANEL_MAX_HEIGHT = 220 + // Longer symbols first so ".." / "<>" / ">=" match before "." / "<" / ">". // Wildcards (* ?) are highlighted in place; @ stays as plain text — none are operands. const WILDCARDS = new Set(['*', '?']) @@ -469,8 +473,12 @@ const FilterInput = ({ const composingRef = useRef(false) const selectingRef = useRef(false) const valueRef = useRef(value ?? '') + const quickFiltersRef = useRef(null) + const quickFilterModalOpenRef = useRef(false) const [focused, setFocused] = useState(false) const [internalValue, setInternalValue] = useState(value ?? '') + const [quickFiltersHeight, setQuickFiltersHeight] = useState(0) + const [quickFilterModalOpen, setQuickFilterModalOpen] = useState(false) const clearTagRoots = useCallback(() => { tagRootsRef.current.forEach((root) => { @@ -864,6 +872,48 @@ const FilterInput = ({ [emitChange, paint] ) + const handleQuickFilterModalOpenChange = useCallback((open) => { + quickFilterModalOpenRef.current = open + setQuickFilterModalOpen(open) + if (open) { + editorRef.current?.blur() + focusedRef.current = false + setFocused(false) + } else { + requestAnimationFrame(() => { + editorRef.current?.focus() + focusedRef.current = true + setFocused(true) + }) + } + }, []) + + const propertyFilterPopoverOpen = focused || quickFilterModalOpen + + useEffect(() => { + if (!propertyFilterEnabled) { + setQuickFiltersHeight(0) + return + } + + const node = quickFiltersRef.current + if (!node) return + + const update = () => { + setQuickFiltersHeight(node.getBoundingClientRect().height) + } + + update() + const observer = new ResizeObserver(update) + observer.observe(node) + return () => observer.disconnect() + }, [ + propertyFilterEnabled, + propertyFilter?.modelType, + propertyFilter?.propertyName, + focused + ]) + const propertyFilterContent = propertyFilterEnabled ? (