Refactor dropdown (#1561)

This commit is contained in:
Charles Bochet 2023-09-13 01:30:33 -07:00 committed by GitHub
parent 84b474c3cc
commit 67f1da038d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 166 additions and 152 deletions

View File

@ -45,6 +45,7 @@ export function FloatingIconButtonGroup({
return (
<FloatingIconButton
key={`floating-icon-button-${index}`}
applyBlur={false}
applyShadow={false}
Icon={Icon}

View File

@ -22,6 +22,7 @@ type OwnProps = {
};
dropdownHotkeyScope?: HotkeyScope;
dropdownPlacement?: Placement;
onDropdownToggle?: (isDropdownOpen: boolean) => void;
};
export function DropdownButton({
@ -31,12 +32,14 @@ export function DropdownButton({
hotkey,
dropdownHotkeyScope,
dropdownPlacement = 'bottom-end',
onDropdownToggle,
}: OwnProps) {
const containerRef = useRef<HTMLDivElement>(null);
const { isDropdownButtonOpen, toggleDropdownButton, closeDropdownButton } =
useDropdownButton({
key: dropdownKey,
onDropdownToggle,
});
const { refs, floatingStyles } = useFloating({

View File

@ -10,7 +10,7 @@ export const StyledHeaderDropdownButton = styled.div<StyledDropdownButtonProps>`
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')};

View File

@ -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 {

View File

@ -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 (
<LightButton
onClick={handleClick}
icon={<IconPlus />}
title="Add filter"
accent="tertiary"
/>
);
}

View File

@ -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<string | null>;
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({
<MultipleFiltersDropdownButton
context={context}
hotkeyScope={hotkeyScope}
Icon={Icon}
isPrimaryButton={isPrimaryButton}
color={color}
label={label}
/>
);
}

View File

@ -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 (
<StyledHeaderDropdownButton
isUnfolded={isDropdownButtonOpen}
onClick={handleClick}
>
Filter
</StyledHeaderDropdownButton>
);
}

View File

@ -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<string | null>;
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 (
<DropdownButton
label={label ?? 'Filter'}
isActive={isFilterSelected}
isUnfolded={isFilterDropdownUnfolded}
Icon={Icon}
onIsUnfoldedChange={handleIsUnfoldedChange}
hotkeyScope={hotkeyScope}
color={color}
menuWidth={
selectedOperandInDropdown &&
filterDefinitionUsedInDropdown?.type === 'date'
? 'auto'
: undefined
}
>
{!filterDefinitionUsedInDropdown ? (
<FilterDropdownFilterSelect context={context} />
) : isFilterDropdownOperandSelectUnfolded ? (
<FilterDropdownOperandSelect context={context} />
) : (
selectedOperandInDropdown && (
<>
<FilterDropdownOperandButton context={context} />
<StyledDropdownMenuSeparator />
{filterDefinitionUsedInDropdown.type === 'text' && (
<FilterDropdownTextSearchInput context={context} />
)}
{filterDefinitionUsedInDropdown.type === 'number' && (
<FilterDropdownNumberSearchInput context={context} />
)}
{filterDefinitionUsedInDropdown.type === 'date' && (
<FilterDropdownDateSearchInput context={context} />
)}
{filterDefinitionUsedInDropdown.type === 'entity' && (
<FilterDropdownEntitySearchInput context={context} />
)}
{filterDefinitionUsedInDropdown.type === 'entity' && (
<FilterDropdownEntitySelect context={context} />
)}
</>
)
)}
</DropdownButton>
dropdownKey={FilterDropdownKey}
buttonComponents={<MultipleFiltersButton />}
dropdownComponents={<MultipleFiltersDropdownContent context={context} />}
onDropdownToggle={() => {
resetState();
}}
/>
);
}

View File

@ -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<string | null>;
};
export function MultipleFiltersDropdownContent({
context,
}: MultipleFiltersDropdownContentProps) {
const [isFilterDropdownOperandSelectUnfolded] = useRecoilScopedState(
isFilterDropdownOperandSelectUnfoldedScopedState,
context,
);
const [filterDefinitionUsedInDropdown] = useRecoilScopedState(
filterDefinitionUsedInDropdownScopedState,
context,
);
const [selectedOperandInDropdown] = useRecoilScopedState(
selectedOperandInDropdownScopedState,
context,
);
return (
<StyledDropdownMenu>
<>
{!filterDefinitionUsedInDropdown ? (
<FilterDropdownFilterSelect context={context} />
) : isFilterDropdownOperandSelectUnfolded ? (
<FilterDropdownOperandSelect context={context} />
) : (
selectedOperandInDropdown && (
<>
<FilterDropdownOperandButton context={context} />
<StyledDropdownMenuSeparator />
{filterDefinitionUsedInDropdown.type === 'text' && (
<FilterDropdownTextSearchInput context={context} />
)}
{filterDefinitionUsedInDropdown.type === 'number' && (
<FilterDropdownNumberSearchInput context={context} />
)}
{filterDefinitionUsedInDropdown.type === 'date' && (
<FilterDropdownDateSearchInput context={context} />
)}
{filterDefinitionUsedInDropdown.type === 'entity' && (
<FilterDropdownEntitySearchInput context={context} />
)}
{filterDefinitionUsedInDropdown.type === 'entity' && (
<FilterDropdownEntitySelect context={context} />
)}
</>
)
)}
</>
</StyledDropdownMenu>
);
}

View File

@ -69,7 +69,6 @@ export const ViewBar = <SortField,>({
<FilterDropdownButton
context={scopeContext}
hotkeyScope={FiltersHotkeyScope.FilterDropdownButton}
isPrimaryButton
/>
<SortDropdownButton<SortField>
context={scopeContext}

View File

@ -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<SortField>({
onReset,
rightComponent,
}: ViewBarDetailsProps) {
const theme = useTheme();
const recoilScopeId = useContextScopeId(context);
const currentViewId = useRecoilScopedValue(currentViewIdScopedState, context);
@ -219,13 +212,7 @@ function ViewBarDetails<SortField>({
</StyledChipcontainer>
{hasFilterButton && (
<StyledAddFilterContainer>
<FilterDropdownButton
context={context}
hotkeyScope={FiltersHotkeyScope.FilterDropdownButton}
color={theme.font.color.tertiary}
Icon={IconPlus}
label="Add filter"
/>
<AddFilterFromDropdownButton />
</StyledAddFilterContainer>
)}
</StyledFilterContainer>

View File

@ -0,0 +1 @@
export const FilterDropdownKey = 'filter';