Skip to content

Commit

Permalink
finalise filter collapse functionality wip
Browse files Browse the repository at this point in the history
  • Loading branch information
Sergej-Vlasov committed Dec 30, 2024
1 parent e459fd9 commit 233ce9d
Show file tree
Hide file tree
Showing 3 changed files with 64 additions and 37 deletions.
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
import { css, cx } from '@emotion/css';
import { GrafanaTheme2 } from '@grafana/data';
import { Icon, Tooltip, useStyles2 } from '@grafana/ui';
import React, { Fragment, memo, useEffect, useRef, useState } from 'react';
import React, { Fragment, memo, useCallback, useLayoutEffect, useMemo, useRef, useState } from 'react';
import { AdHocFiltersVariable } from '../AdHocFiltersVariable';
import { AdHocFilterPill } from './AdHocFilterPill';
import { AdHocFiltersAlwaysWipCombobox } from './AdHocFiltersAlwaysWipCombobox';
import { debounce } from 'lodash';
import { calculateCollapseThreshold } from './utils';

interface Props {
model: AdHocFiltersVariable;
Expand All @@ -14,54 +15,56 @@ interface Props {
export const AdHocFiltersComboboxRenderer = memo(function AdHocFiltersComboboxRenderer({ model }: Props) {
const { filters, readOnly } = model.useState();
const styles = useStyles2(getStyles);
const [limitFiltersTo, setLimitFiltersTo] = useState<number | null>(null);
const wrapperRef = useRef<HTMLDivElement>(null);

const handleCollapseFilters = (shouldCollapse: boolean) => {
if (!shouldCollapse) {
setLimitFiltersTo(null);
return;
}
if (wrapperRef.current) {
const rect = wrapperRef.current.getBoundingClientRect();
if (rect.height - 6 > 26) {
const componentLineSpan = (rect.height - 6) / 26;
const filterCutOff = Math.max(1, Math.floor(filters.length / (componentLineSpan + 1)));
setLimitFiltersTo(filterCutOff);
} else {
setLimitFiltersTo(null);
}
}
};
// ref that focuses on the always wip filter input
// defined in the combobox component via useImperativeHandle
const focusOnWipInputRef = useRef<() => void>();

const [collapseThreshold, setCollapseThreshold] = useState<number | null>(null);

const updateCollapseThreshold = useCallback(
(shouldCollapse: boolean, filtersLength: number) => {
const filterCollapseThreshold = calculateCollapseThreshold(
!readOnly ? shouldCollapse : false,
filtersLength,
wrapperRef
);
setCollapseThreshold(filterCollapseThreshold);
},
[readOnly]
);

const debouncedSetActive = useMemo(() => debounce(updateCollapseThreshold, 100), [updateCollapseThreshold]);

const debouncedSetActive = debounce(handleCollapseFilters, 100);
const handleFilterCollapse = useCallback(
(shouldCollapse: boolean, filtersLength: number) => () => {
debouncedSetActive(shouldCollapse, filtersLength);
},
[debouncedSetActive]
);

useEffect(() => {
handleCollapseFilters(true);
useLayoutEffect(() => {
// updateCollapseThreshold(!!model.state.collapseFilters ? true : false, filters.length);
updateCollapseThreshold(true, filters.length);
// needs to run only on first render
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);

// ref that focuses on the always wip filter input
// defined in the combobox component via useImperativeHandle
const focusOnWipInputRef = useRef<() => void>();

return (
<div
className={cx(styles.comboboxWrapper, { [styles.comboboxFocusOutline]: !readOnly })}
onClick={() => {
focusOnWipInputRef.current?.();
}}
ref={wrapperRef}
onFocusCapture={(e) => {
debouncedSetActive(false);
}}
onBlurCapture={(e) => {
debouncedSetActive(true);
}}
onFocusCapture={handleFilterCollapse(false, filters.length)}
// onBlurCapture={handleFilterCollapse(!!model.state.collapseFilters ? true : false, filters.length}
onBlurCapture={handleFilterCollapse(true, filters.length)}
>
<Icon name="filter" className={styles.filterIcon} size="lg" />

{(limitFiltersTo ? filters.slice(0, limitFiltersTo) : filters).map((filter, index) => (
{(collapseThreshold ? filters.slice(0, collapseThreshold) : filters).map((filter, index) => (
<AdHocFilterPill
key={`${index}-${filter.key}`}
filter={filter}
Expand All @@ -71,14 +74,12 @@ export const AdHocFiltersComboboxRenderer = memo(function AdHocFiltersComboboxRe
/>
))}

{limitFiltersTo ? (
{collapseThreshold ? (
<Tooltip
content={
<div>
{filters.slice(limitFiltersTo).map((filter, i) => {
{filters.slice(collapseThreshold).map((filter, i) => {
const keyLabel = filter.keyLabel ?? filter.key;
// TODO remove when we're on the latest version of @grafana/data
//@ts-expect-error
const valueLabel = filter.valueLabels?.join(', ') || filter.values?.join(', ') || filter.value;
return (
<Fragment key={`${keyLabel}-${i}`}>
Expand All @@ -89,7 +90,7 @@ export const AdHocFiltersComboboxRenderer = memo(function AdHocFiltersComboboxRe
</div>
}
>
<div className={cx(styles.basePill)}>+{filters.length - limitFiltersTo} filters </div>
<div className={cx(styles.basePill)}>+{filters.length - collapseThreshold} filters </div>
</Tooltip>
) : null}

Expand Down
21 changes: 21 additions & 0 deletions packages/scenes/src/variables/adhoc/AdHocFiltersCombobox/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -211,3 +211,24 @@ export const populateInputValueOnInputTypeSwitch = ({
setInputValue('');
}
};

export const calculateCollapseThreshold = (
shouldCollapse: boolean,
filtersLength: number,
wrapperRef: React.RefObject<HTMLDivElement>
) => {
if (!shouldCollapse || !wrapperRef.current) {
return null;
}

const rect = wrapperRef.current.getBoundingClientRect();

if (rect.height - 6 > 26) {
const componentLineSpan = (rect.height - 6) / 26;
const filterCutOff = Math.max(1, Math.floor(filtersLength / (componentLineSpan + 1)));

return filterCutOff;
}

return null;
};
5 changes: 5 additions & 0 deletions packages/scenes/src/variables/adhoc/AdHocFiltersVariable.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,11 @@ export interface AdHocFiltersVariableState extends SceneVariableState {
* @internal state of the new filter being added
*/
_wip?: AdHocFilterWithLabels;

/**
* Flag that will collapse combobox filters when they become too long
*/
collapseFilters?: boolean;
}

export type AdHocVariableExpressionBuilderFn = (filters: AdHocFilterWithLabels[]) => string;
Expand Down

0 comments on commit 233ce9d

Please sign in to comment.