diff --git a/.changeset/spotty-suits-own.md b/.changeset/spotty-suits-own.md new file mode 100644 index 000000000..65e20d100 --- /dev/null +++ b/.changeset/spotty-suits-own.md @@ -0,0 +1,5 @@ +--- +"@hyperdx/app": minor +--- + +Feat: add highlight animation for recently moved filter checkboxes diff --git a/packages/app/src/components/DBSearchPageFilters.tsx b/packages/app/src/components/DBSearchPageFilters.tsx index 2c6489e5e..6482fc28e 100644 --- a/packages/app/src/components/DBSearchPageFilters.tsx +++ b/packages/app/src/components/DBSearchPageFilters.tsx @@ -1,4 +1,13 @@ -import { memo, useCallback, useEffect, useId, useMemo, useState } from 'react'; +import { + memo, + useCallback, + useEffect, + useId, + useMemo, + useRef, + useState, +} from 'react'; +import cx from 'classnames'; import { TableMetadata, tcFromSource, @@ -65,6 +74,7 @@ type FilterCheckboxProps = { onClickOnly?: VoidFunction; onClickExclude?: VoidFunction; onClickPin: VoidFunction; + className?: string; }; export const TextButton = ({ @@ -103,6 +113,7 @@ export const FilterCheckbox = ({ onClickOnly, onClickExclude, onClickPin, + className, }: FilterCheckboxProps) => { return (
>(new Set()); useEffect(() => { if (isDefaultExpanded) { @@ -298,6 +311,27 @@ export const FilterGroup = ({ totalFiltersSize, ]); + // Simple highlight animation when checkbox is checked + const handleChange = useCallback( + (value: string) => { + const wasIncluded = selectedValues.included.has(value); + + // If checking (not unchecking), trigger highlight animation + if (!wasIncluded) { + setRecentlyMoved(prev => new Set(prev).add(value)); + setTimeout(() => { + setRecentlyMoved(prev => { + const newSet = new Set(prev); + newSet.delete(value); + return newSet; + }); + }, 600); + } + + onChange(value); + }, + [onChange, selectedValues], + ); const showShowMoreButton = !search && augmentedOptions.length > MAX_FILTER_GROUP_ITEMS && @@ -403,6 +437,9 @@ export const FilterGroup = ({ key={option.value} label={option.label} pinned={isPinned(option.value)} + className={ + recentlyMoved.has(option.value) ? classes.recentlyMoved : '' + } value={ selectedValues.included.has(option.value) ? 'included' @@ -410,7 +447,7 @@ export const FilterGroup = ({ ? 'excluded' : false } - onChange={() => onChange(option.value)} + onChange={() => handleChange(option.value)} onClickOnly={() => onOnlyClick(option.value)} onClickExclude={() => onExcludeClick(option.value)} onClickPin={() => onPinClick(option.value)} diff --git a/packages/app/styles/SearchPage.module.scss b/packages/app/styles/SearchPage.module.scss index 9854b17d8..cb664da39 100644 --- a/packages/app/styles/SearchPage.module.scss +++ b/packages/app/styles/SearchPage.module.scss @@ -130,3 +130,24 @@ transform: rotate(0deg); } } + +.recentlyMoved { + animation: highlight 0.6s ease-out; +} + +@keyframes highlight { + 0% { + background-color: $slate-950; + transform: scale(1.02); + } + + 50% { + background-color: $slate-900; + transform: scale(1.01); + } + + 100% { + background-color: transparent; + transform: scale(1); + } +}