diff --git a/front/src/modules/ui/button/components/FloatingIconButtonGroup.tsx b/front/src/modules/ui/button/components/FloatingIconButtonGroup.tsx index 32191626c..9a0d08656 100644 --- a/front/src/modules/ui/button/components/FloatingIconButtonGroup.tsx +++ b/front/src/modules/ui/button/components/FloatingIconButtonGroup.tsx @@ -45,6 +45,7 @@ export function FloatingIconButtonGroup({ return ( void; }; export function DropdownButton({ @@ -31,12 +32,14 @@ export function DropdownButton({ hotkey, dropdownHotkeyScope, dropdownPlacement = 'bottom-end', + onDropdownToggle, }: OwnProps) { const containerRef = useRef(null); const { isDropdownButtonOpen, toggleDropdownButton, closeDropdownButton } = useDropdownButton({ key: dropdownKey, + onDropdownToggle, }); const { refs, floatingStyles } = useFloating({ diff --git a/front/src/modules/ui/dropdown/components/StyledHeaderDropdownButton.tsx b/front/src/modules/ui/dropdown/components/StyledHeaderDropdownButton.tsx index 972e57330..150898cc3 100644 --- a/front/src/modules/ui/dropdown/components/StyledHeaderDropdownButton.tsx +++ b/front/src/modules/ui/dropdown/components/StyledHeaderDropdownButton.tsx @@ -10,7 +10,7 @@ export const StyledHeaderDropdownButton = styled.div` background: ${({ theme }) => theme.background.primary}; border-radius: ${({ theme }) => theme.border.radius.sm}; color: ${({ isActive, theme, color }) => - color ?? (isActive ? theme.color.blue : 'none')}; + color ?? (isActive ? theme.color.blue : theme.font.color.secondary)}; cursor: pointer; display: flex; filter: ${(props) => (props.isUnfolded ? 'brightness(0.95)' : 'none')}; diff --git a/front/src/modules/ui/dropdown/hooks/useDropdownButton.ts b/front/src/modules/ui/dropdown/hooks/useDropdownButton.ts index cecb886f5..15230cd67 100644 --- a/front/src/modules/ui/dropdown/hooks/useDropdownButton.ts +++ b/front/src/modules/ui/dropdown/hooks/useDropdownButton.ts @@ -6,7 +6,13 @@ import { isDropdownButtonOpenScopedFamilyState } from '../states/isDropdownButto import { DropdownRecoilScopeContext } from '../states/recoil-scope-contexts/DropdownRecoilScopeContext'; // TODO: have a more explicit name than key -export function useDropdownButton({ key }: { key: string }) { +export function useDropdownButton({ + key, + onDropdownToggle, +}: { + key: string; + onDropdownToggle?: (isDropdownButtonOpen: boolean) => void; +}) { const { setHotkeyScopeAndMemorizePreviousScope, goBackToPreviousHotkeyScope, @@ -28,6 +34,7 @@ export function useDropdownButton({ key }: { key: string }) { function closeDropdownButton() { goBackToPreviousHotkeyScope(); setIsDropdownButtonOpen(false); + onDropdownToggle?.(false); } function openDropdownButton() { @@ -39,6 +46,7 @@ export function useDropdownButton({ key }: { key: string }) { dropdownButtonCustomHotkeyScope.customScopes, ); } + onDropdownToggle?.(true); } function toggleDropdownButton() { @@ -47,6 +55,7 @@ export function useDropdownButton({ key }: { key: string }) { } else { openDropdownButton(); } + onDropdownToggle?.(isDropdownButtonOpen); } return { diff --git a/front/src/modules/ui/view-bar/components/AddFilterFromDetailsButton.tsx b/front/src/modules/ui/view-bar/components/AddFilterFromDetailsButton.tsx new file mode 100644 index 000000000..097d8233c --- /dev/null +++ b/front/src/modules/ui/view-bar/components/AddFilterFromDetailsButton.tsx @@ -0,0 +1,24 @@ +import { LightButton } from '@/ui/button/components/LightButton'; +import { useDropdownButton } from '@/ui/dropdown/hooks/useDropdownButton'; +import { IconPlus } from '@/ui/icon'; + +import { FilterDropdownKey } from '../types/FilterDropdownKey'; + +export function AddFilterFromDropdownButton() { + const { toggleDropdownButton } = useDropdownButton({ + key: FilterDropdownKey, + }); + + function handleClick() { + toggleDropdownButton(); + } + + return ( + } + title="Add filter" + accent="tertiary" + /> + ); +} diff --git a/front/src/modules/ui/view-bar/components/FilterDropdownButton.tsx b/front/src/modules/ui/view-bar/components/FilterDropdownButton.tsx index 36d9cd3cc..d8b69f87f 100644 --- a/front/src/modules/ui/view-bar/components/FilterDropdownButton.tsx +++ b/front/src/modules/ui/view-bar/components/FilterDropdownButton.tsx @@ -1,6 +1,5 @@ import { Context } from 'react'; -import { IconComponent } from '@/ui/icon/types/IconComponent'; import { useRecoilScopedState } from '@/ui/utilities/recoil-scope/hooks/useRecoilScopedState'; import { availableFiltersScopedState } from '../states/availableFiltersScopedState'; @@ -11,19 +10,11 @@ import { SingleEntityFilterDropdownButton } from './SingleEntityFilterDropdownBu type FilterDropdownButtonProps = { context: Context; hotkeyScope: string; - isPrimaryButton?: boolean; - Icon?: IconComponent; - color?: string; - label?: string; }; export function FilterDropdownButton({ context, hotkeyScope, - isPrimaryButton = false, - color, - Icon, - label, }: FilterDropdownButtonProps) { const [availableFilters] = useRecoilScopedState( availableFiltersScopedState, @@ -46,10 +37,6 @@ export function FilterDropdownButton({ ); } diff --git a/front/src/modules/ui/view-bar/components/MultipleFiltersButton.tsx b/front/src/modules/ui/view-bar/components/MultipleFiltersButton.tsx new file mode 100644 index 000000000..9e2da82e6 --- /dev/null +++ b/front/src/modules/ui/view-bar/components/MultipleFiltersButton.tsx @@ -0,0 +1,23 @@ +import { StyledHeaderDropdownButton } from '@/ui/dropdown/components/StyledHeaderDropdownButton'; +import { useDropdownButton } from '@/ui/dropdown/hooks/useDropdownButton'; + +import { FilterDropdownKey } from '../types/FilterDropdownKey'; + +export function MultipleFiltersButton() { + const { isDropdownButtonOpen, toggleDropdownButton } = useDropdownButton({ + key: FilterDropdownKey, + }); + + function handleClick() { + toggleDropdownButton(); + } + + return ( + + Filter + + ); +} diff --git a/front/src/modules/ui/view-bar/components/MultipleFiltersDropdownButton.tsx b/front/src/modules/ui/view-bar/components/MultipleFiltersDropdownButton.tsx index e1d1ff34f..d108f3bce 100644 --- a/front/src/modules/ui/view-bar/components/MultipleFiltersDropdownButton.tsx +++ b/front/src/modules/ui/view-bar/components/MultipleFiltersDropdownButton.tsx @@ -1,72 +1,44 @@ import { Context, useCallback } from 'react'; -import { StyledDropdownMenuSeparator } from '@/ui/dropdown/components/StyledDropdownMenuSeparator'; -import { DropdownRecoilScopeContext } from '@/ui/dropdown/states/recoil-scope-contexts/DropdownRecoilScopeContext'; -import { IconComponent } from '@/ui/icon/types/IconComponent'; -import { useSetHotkeyScope } from '@/ui/utilities/hotkey/hooks/useSetHotkeyScope'; +import { DropdownButton } from '@/ui/dropdown/components/DropdownButton'; import { useRecoilScopedState } from '@/ui/utilities/recoil-scope/hooks/useRecoilScopedState'; -import { filterDefinitionUsedInDropdownScopedState } from '@/ui/view-bar/states/filterDefinitionUsedInDropdownScopedState'; -import { filterDropdownSearchInputScopedState } from '@/ui/view-bar/states/filterDropdownSearchInputScopedState'; -import { filtersScopedState } from '@/ui/view-bar/states/filtersScopedState'; -import { isFilterDropdownOperandSelectUnfoldedScopedState } from '@/ui/view-bar/states/isFilterDropdownOperandSelectUnfoldedScopedState'; -import { selectedOperandInDropdownScopedState } from '@/ui/view-bar/states/selectedOperandInDropdownScopedState'; -import { isFilterDropdownUnfoldedScopedState } from '../states/isFilterDropdownUnfoldedScopedState'; -import { isViewBarExpandedScopedState } from '../states/isViewBarExpandedScopedState'; +import { filterDefinitionUsedInDropdownScopedState } from '../states/filterDefinitionUsedInDropdownScopedState'; +import { filterDropdownSearchInputScopedState } from '../states/filterDropdownSearchInputScopedState'; +import { isFilterDropdownOperandSelectUnfoldedScopedState } from '../states/isFilterDropdownOperandSelectUnfoldedScopedState'; +import { selectedOperandInDropdownScopedState } from '../states/selectedOperandInDropdownScopedState'; +import { FilterDropdownKey } from '../types/FilterDropdownKey'; -import DropdownButton from './DropdownButton'; -import { FilterDropdownDateSearchInput } from './FilterDropdownDateSearchInput'; -import { FilterDropdownEntitySearchInput } from './FilterDropdownEntitySearchInput'; -import { FilterDropdownEntitySelect } from './FilterDropdownEntitySelect'; -import { FilterDropdownFilterSelect } from './FilterDropdownFilterSelect'; -import { FilterDropdownNumberSearchInput } from './FilterDropdownNumberSearchInput'; -import { FilterDropdownOperandButton } from './FilterDropdownOperandButton'; -import { FilterDropdownOperandSelect } from './FilterDropdownOperandSelect'; -import { FilterDropdownTextSearchInput } from './FilterDropdownTextSearchInput'; +import { MultipleFiltersButton } from './MultipleFiltersButton'; +import { MultipleFiltersDropdownContent } from './MultipleFiltersDropdownContent'; type MultipleFiltersDropdownButtonProps = { context: Context; hotkeyScope: string; - isPrimaryButton?: boolean; - Icon?: IconComponent; - color?: string; - label?: string; }; export function MultipleFiltersDropdownButton({ context, - hotkeyScope, - isPrimaryButton = false, - color, - Icon, - label, }: MultipleFiltersDropdownButtonProps) { - const [isFilterDropdownUnfolded, setIsFilterDropdownUnfolded] = - useRecoilScopedState( - isFilterDropdownUnfoldedScopedState, - DropdownRecoilScopeContext, - ); - - const [ - isFilterDropdownOperandSelectUnfolded, - setIsFilterDropdownOperandSelectUnfolded, - ] = useRecoilScopedState( + const [, setIsFilterDropdownOperandSelectUnfolded] = useRecoilScopedState( isFilterDropdownOperandSelectUnfoldedScopedState, context, ); - const [filterDefinitionUsedInDropdown, setFilterDefinitionUsedInDropdown] = - useRecoilScopedState(filterDefinitionUsedInDropdownScopedState, context); + const [, setFilterDefinitionUsedInDropdown] = useRecoilScopedState( + filterDefinitionUsedInDropdownScopedState, + context, + ); const [, setFilterDropdownSearchInput] = useRecoilScopedState( filterDropdownSearchInputScopedState, context, ); - const [filters] = useRecoilScopedState(filtersScopedState, context); - - const [selectedOperandInDropdown, setSelectedOperandInDropdown] = - useRecoilScopedState(selectedOperandInDropdownScopedState, context); + const [, setSelectedOperandInDropdown] = useRecoilScopedState( + selectedOperandInDropdownScopedState, + context, + ); const resetState = useCallback(() => { setIsFilterDropdownOperandSelectUnfolded(false); @@ -79,81 +51,14 @@ export function MultipleFiltersDropdownButton({ setFilterDropdownSearchInput, setIsFilterDropdownOperandSelectUnfolded, ]); - - const isFilterSelected = (filters?.length ?? 0) > 0; - - const setHotkeyScope = useSetHotkeyScope(); - - const [isViewBarExpanded, setIsViewBarExpanded] = useRecoilScopedState( - isViewBarExpandedScopedState, - context, - ); - - function handleIsUnfoldedChange(unfolded: boolean) { - if (unfolded && isPrimaryButton) { - setIsViewBarExpanded(!isViewBarExpanded); - } - - if ( - unfolded && - ((isPrimaryButton && !isFilterSelected) || !isPrimaryButton) - ) { - setHotkeyScope(hotkeyScope); - setIsFilterDropdownUnfolded(true); - return; - } - - if (filterDefinitionUsedInDropdown?.type === 'entity') { - setHotkeyScope(hotkeyScope); - } - - setIsFilterDropdownUnfolded(false); - resetState(); - } - return ( - {!filterDefinitionUsedInDropdown ? ( - - ) : isFilterDropdownOperandSelectUnfolded ? ( - - ) : ( - selectedOperandInDropdown && ( - <> - - - {filterDefinitionUsedInDropdown.type === 'text' && ( - - )} - {filterDefinitionUsedInDropdown.type === 'number' && ( - - )} - {filterDefinitionUsedInDropdown.type === 'date' && ( - - )} - {filterDefinitionUsedInDropdown.type === 'entity' && ( - - )} - {filterDefinitionUsedInDropdown.type === 'entity' && ( - - )} - - ) - )} - + dropdownKey={FilterDropdownKey} + buttonComponents={} + dropdownComponents={} + onDropdownToggle={() => { + resetState(); + }} + /> ); } diff --git a/front/src/modules/ui/view-bar/components/MultipleFiltersDropdownContent.tsx b/front/src/modules/ui/view-bar/components/MultipleFiltersDropdownContent.tsx new file mode 100644 index 000000000..a2e9ab7fb --- /dev/null +++ b/front/src/modules/ui/view-bar/components/MultipleFiltersDropdownContent.tsx @@ -0,0 +1,75 @@ +import { Context } from 'react'; + +import { StyledDropdownMenu } from '@/ui/dropdown/components/StyledDropdownMenu'; +import { StyledDropdownMenuSeparator } from '@/ui/dropdown/components/StyledDropdownMenuSeparator'; +import { useRecoilScopedState } from '@/ui/utilities/recoil-scope/hooks/useRecoilScopedState'; + +import { filterDefinitionUsedInDropdownScopedState } from '../states/filterDefinitionUsedInDropdownScopedState'; +import { isFilterDropdownOperandSelectUnfoldedScopedState } from '../states/isFilterDropdownOperandSelectUnfoldedScopedState'; +import { selectedOperandInDropdownScopedState } from '../states/selectedOperandInDropdownScopedState'; + +import { FilterDropdownDateSearchInput } from './FilterDropdownDateSearchInput'; +import { FilterDropdownEntitySearchInput } from './FilterDropdownEntitySearchInput'; +import { FilterDropdownEntitySelect } from './FilterDropdownEntitySelect'; +import { FilterDropdownFilterSelect } from './FilterDropdownFilterSelect'; +import { FilterDropdownNumberSearchInput } from './FilterDropdownNumberSearchInput'; +import { FilterDropdownOperandButton } from './FilterDropdownOperandButton'; +import { FilterDropdownOperandSelect } from './FilterDropdownOperandSelect'; +import { FilterDropdownTextSearchInput } from './FilterDropdownTextSearchInput'; + +export type MultipleFiltersDropdownContentProps = { + context: Context; +}; + +export function MultipleFiltersDropdownContent({ + context, +}: MultipleFiltersDropdownContentProps) { + const [isFilterDropdownOperandSelectUnfolded] = useRecoilScopedState( + isFilterDropdownOperandSelectUnfoldedScopedState, + context, + ); + + const [filterDefinitionUsedInDropdown] = useRecoilScopedState( + filterDefinitionUsedInDropdownScopedState, + context, + ); + + const [selectedOperandInDropdown] = useRecoilScopedState( + selectedOperandInDropdownScopedState, + context, + ); + + return ( + + <> + {!filterDefinitionUsedInDropdown ? ( + + ) : isFilterDropdownOperandSelectUnfolded ? ( + + ) : ( + selectedOperandInDropdown && ( + <> + + + {filterDefinitionUsedInDropdown.type === 'text' && ( + + )} + {filterDefinitionUsedInDropdown.type === 'number' && ( + + )} + {filterDefinitionUsedInDropdown.type === 'date' && ( + + )} + {filterDefinitionUsedInDropdown.type === 'entity' && ( + + )} + {filterDefinitionUsedInDropdown.type === 'entity' && ( + + )} + + ) + )} + + + ); +} diff --git a/front/src/modules/ui/view-bar/components/ViewBar.tsx b/front/src/modules/ui/view-bar/components/ViewBar.tsx index f1f1ca286..e8cc97f2c 100644 --- a/front/src/modules/ui/view-bar/components/ViewBar.tsx +++ b/front/src/modules/ui/view-bar/components/ViewBar.tsx @@ -69,7 +69,6 @@ export const ViewBar = ({ context={scopeContext} diff --git a/front/src/modules/ui/view-bar/components/ViewBarDetails.tsx b/front/src/modules/ui/view-bar/components/ViewBarDetails.tsx index fe916586a..853ce4aa3 100644 --- a/front/src/modules/ui/view-bar/components/ViewBarDetails.tsx +++ b/front/src/modules/ui/view-bar/components/ViewBarDetails.tsx @@ -1,13 +1,8 @@ import type { Context, ReactNode } from 'react'; -import { useTheme } from '@emotion/react'; import styled from '@emotion/styled'; import { useRecoilValue } from 'recoil'; -import { - IconArrowNarrowDown, - IconArrowNarrowUp, - IconPlus, -} from '@/ui/icon/index'; +import { IconArrowNarrowDown, IconArrowNarrowUp } from '@/ui/icon/index'; import { useContextScopeId } from '@/ui/utilities/recoil-scope/hooks/useContextScopeId'; import { useRecoilScopedState } from '@/ui/utilities/recoil-scope/hooks/useRecoilScopedState'; import { useRecoilScopedValue } from '@/ui/utilities/recoil-scope/hooks/useRecoilScopedValue'; @@ -20,11 +15,10 @@ import { isViewBarExpandedScopedState } from '../states/isViewBarExpandedScopedS import { canPersistFiltersScopedFamilySelector } from '../states/selectors/canPersistFiltersScopedFamilySelector'; import { canPersistSortsScopedFamilySelector } from '../states/selectors/canPersistSortsScopedFamilySelector'; import { sortsScopedState } from '../states/sortsScopedState'; -import { FiltersHotkeyScope } from '../types/FiltersHotkeyScope'; import { SelectedSortType } from '../types/interface'; import { getOperandLabelShort } from '../utils/getOperandLabel'; -import { FilterDropdownButton } from './FilterDropdownButton'; +import { AddFilterFromDropdownButton } from './AddFilterFromDetailsButton'; import SortOrFilterChip from './SortOrFilterChip'; export type ViewBarDetailsProps = { @@ -99,6 +93,7 @@ const StyledSeperator = styled.div` `; const StyledAddFilterContainer = styled.div` + margin-left: ${({ theme }) => theme.spacing(1)}; z-index: 5; `; @@ -109,8 +104,6 @@ function ViewBarDetails({ onReset, rightComponent, }: ViewBarDetailsProps) { - const theme = useTheme(); - const recoilScopeId = useContextScopeId(context); const currentViewId = useRecoilScopedValue(currentViewIdScopedState, context); @@ -219,13 +212,7 @@ function ViewBarDetails({ {hasFilterButton && ( - + )} diff --git a/front/src/modules/ui/view-bar/types/FilterDropdownKey.ts b/front/src/modules/ui/view-bar/types/FilterDropdownKey.ts new file mode 100644 index 000000000..b10e7a802 --- /dev/null +++ b/front/src/modules/ui/view-bar/types/FilterDropdownKey.ts @@ -0,0 +1 @@ +export const FilterDropdownKey = 'filter';