feat: I can hide/show filter bar and add filters directly from filter bar (#1173)

* I can hide/show filter bar and add filters directly from filter bar

Co-authored-by: v1b3m <vibenjamin6@gmail.com>

* Add requested changes

Co-authored-by: v1b3m <vibenjamin6@gmail.com>

* Revert breaking changes

Co-authored-by: v1b3m <vibenjamin6@gmail.com>

---------

Co-authored-by: v1b3m <vibenjamin6@gmail.com>
This commit is contained in:
gitstart-twenty
2023-08-15 10:12:11 +08:00
committed by GitHub
parent 239d036813
commit 656f1af15c
8 changed files with 129 additions and 44 deletions

View File

@ -71,6 +71,7 @@ export function BoardHeader<SortField>({
HotkeyScope={FiltersHotkeyScope.FilterDropdownButton} HotkeyScope={FiltersHotkeyScope.FilterDropdownButton}
/> />
<SortDropdownButton<SortField> <SortDropdownButton<SortField>
context={context}
isSortSelected={sorts.length > 0} isSortSelected={sorts.length > 0}
availableSorts={availableSorts || []} availableSorts={availableSorts || []}
onSortSelect={sortSelect} onSortSelect={sortSelect}

View File

@ -16,6 +16,7 @@ type OwnProps = {
onIsUnfoldedChange?: (newIsUnfolded: boolean) => void; onIsUnfoldedChange?: (newIsUnfolded: boolean) => void;
resetState?: () => void; resetState?: () => void;
HotkeyScope: FiltersHotkeyScope; HotkeyScope: FiltersHotkeyScope;
color?: string;
}; };
const StyledDropdownButtonContainer = styled.div` const StyledDropdownButtonContainer = styled.div`
@ -33,7 +34,8 @@ type StyledDropdownButtonProps = {
const StyledDropdownButton = styled.div<StyledDropdownButtonProps>` const StyledDropdownButton = styled.div<StyledDropdownButtonProps>`
background: ${({ theme }) => theme.background.primary}; background: ${({ theme }) => theme.background.primary};
border-radius: ${({ theme }) => theme.border.radius.sm}; border-radius: ${({ theme }) => theme.border.radius.sm};
color: ${(props) => (props.isActive ? props.theme.color.blue : 'none')}; color: ${({ isActive, theme, color }) =>
color ?? (isActive ? theme.color.blue : 'none')};
cursor: pointer; cursor: pointer;
display: flex; display: flex;
filter: ${(props) => (props.isUnfolded ? 'brightness(0.95)' : 'none')}; filter: ${(props) => (props.isUnfolded ? 'brightness(0.95)' : 'none')};
@ -56,6 +58,7 @@ function DropdownButton({
isUnfolded = false, isUnfolded = false,
onIsUnfoldedChange, onIsUnfoldedChange,
HotkeyScope, HotkeyScope,
color,
}: OwnProps) { }: OwnProps) {
useScopedHotkeys( useScopedHotkeys(
[Key.Enter, Key.Escape], [Key.Enter, Key.Escape],
@ -81,6 +84,7 @@ function DropdownButton({
onClick={onButtonClick} onClick={onButtonClick}
isActive={isActive} isActive={isActive}
aria-selected={isActive} aria-selected={isActive}
color={color}
> >
{label} {label}
</StyledDropdownButton> </StyledDropdownButton>

View File

@ -11,9 +11,15 @@ import { SingleEntityFilterDropdownButton } from './SingleEntityFilterDropdownBu
export function FilterDropdownButton({ export function FilterDropdownButton({
context, context,
HotkeyScope, HotkeyScope,
isPrimaryButton = false,
color,
label,
}: { }: {
context: Context<string | null>; context: Context<string | null>;
HotkeyScope: FiltersHotkeyScope; HotkeyScope: FiltersHotkeyScope;
isPrimaryButton?: boolean;
color?: string;
label?: string;
}) { }) {
const [availableFilters] = useRecoilScopedState( const [availableFilters] = useRecoilScopedState(
availableFiltersScopedState, availableFiltersScopedState,
@ -29,6 +35,9 @@ export function FilterDropdownButton({
<MultipleFiltersDropdownButton <MultipleFiltersDropdownButton
context={context} context={context}
HotkeyScope={HotkeyScope} HotkeyScope={HotkeyScope}
isPrimaryButton={isPrimaryButton}
color={color}
label={label}
/> />
); );
} }

View File

@ -9,6 +9,7 @@ import { selectedOperandInDropdownScopedState } from '@/ui/filter-n-sort/states/
import { useSetHotkeyScope } from '@/ui/utilities/hotkey/hooks/useSetHotkeyScope'; import { useSetHotkeyScope } from '@/ui/utilities/hotkey/hooks/useSetHotkeyScope';
import { useRecoilScopedState } from '@/ui/utilities/recoil-scope/hooks/useRecoilScopedState'; import { useRecoilScopedState } from '@/ui/utilities/recoil-scope/hooks/useRecoilScopedState';
import { sortAndFilterBarScopedState } from '../states/sortAndFilterBarScopedState';
import { FiltersHotkeyScope } from '../types/FiltersHotkeyScope'; import { FiltersHotkeyScope } from '../types/FiltersHotkeyScope';
import DropdownButton from './DropdownButton'; import DropdownButton from './DropdownButton';
@ -24,9 +25,15 @@ import { FilterDropdownTextSearchInput } from './FilterDropdownTextSearchInput';
export function MultipleFiltersDropdownButton({ export function MultipleFiltersDropdownButton({
context, context,
HotkeyScope, HotkeyScope,
isPrimaryButton = false,
color,
label,
}: { }: {
context: Context<string | null>; context: Context<string | null>;
HotkeyScope: FiltersHotkeyScope; HotkeyScope: FiltersHotkeyScope;
isPrimaryButton?: boolean;
color?: string;
label?: string;
}) { }) {
const [isUnfolded, setIsUnfolded] = useState(false); const [isUnfolded, setIsUnfolded] = useState(false);
@ -67,10 +74,16 @@ export function MultipleFiltersDropdownButton({
const setHotkeyScope = useSetHotkeyScope(); const setHotkeyScope = useSetHotkeyScope();
const [isSortAndFilterBarOpen, setIsSortAndFilterBarOpen] =
useRecoilScopedState(sortAndFilterBarScopedState, context);
function handleIsUnfoldedChange(newIsUnfolded: boolean) { function handleIsUnfoldedChange(newIsUnfolded: boolean) {
if (newIsUnfolded) { if (newIsUnfolded && (!isFilterSelected || !isPrimaryButton)) {
setHotkeyScope(HotkeyScope); setHotkeyScope(HotkeyScope);
setIsUnfolded(true); setIsUnfolded(true);
setIsSortAndFilterBarOpen(true);
} else if (newIsUnfolded && isFilterSelected && isPrimaryButton) {
setIsSortAndFilterBarOpen(!isSortAndFilterBarOpen);
} else { } else {
if (filterDefinitionUsedInDropdown?.type === 'entity') { if (filterDefinitionUsedInDropdown?.type === 'entity') {
setHotkeyScope(HotkeyScope); setHotkeyScope(HotkeyScope);
@ -82,11 +95,12 @@ export function MultipleFiltersDropdownButton({
return ( return (
<DropdownButton <DropdownButton
label="Filter" label={label ?? 'Filter'}
isActive={isFilterSelected} isActive={isFilterSelected}
isUnfolded={isUnfolded} isUnfolded={isUnfolded}
onIsUnfoldedChange={handleIsUnfoldedChange} onIsUnfoldedChange={handleIsUnfoldedChange}
HotkeyScope={HotkeyScope} HotkeyScope={HotkeyScope}
color={color}
> >
{!filterDefinitionUsedInDropdown ? ( {!filterDefinitionUsedInDropdown ? (
<FilterDropdownFilterSelect context={context} /> <FilterDropdownFilterSelect context={context} />

View File

@ -8,9 +8,12 @@ import { useRecoilScopedState } from '@/ui/utilities/recoil-scope/hooks/useRecoi
import { useRemoveFilter } from '../hooks/useRemoveFilter'; import { useRemoveFilter } from '../hooks/useRemoveFilter';
import { availableFiltersScopedState } from '../states/availableFiltersScopedState'; import { availableFiltersScopedState } from '../states/availableFiltersScopedState';
import { filtersScopedState } from '../states/filtersScopedState'; import { filtersScopedState } from '../states/filtersScopedState';
import { sortAndFilterBarScopedState } from '../states/sortAndFilterBarScopedState';
import { FiltersHotkeyScope } from '../types/FiltersHotkeyScope';
import { SelectedSortType } from '../types/interface'; import { SelectedSortType } from '../types/interface';
import { getOperandLabelShort } from '../utils/getOperandLabel'; import { getOperandLabelShort } from '../utils/getOperandLabel';
import { FilterDropdownButton } from './FilterDropdownButton';
import SortOrFilterChip from './SortOrFilterChip'; import SortOrFilterChip from './SortOrFilterChip';
type OwnProps<SortField> = { type OwnProps<SortField> = {
@ -18,6 +21,7 @@ type OwnProps<SortField> = {
sorts: Array<SelectedSortType<SortField>>; sorts: Array<SelectedSortType<SortField>>;
onRemoveSort: (sortId: SelectedSortType<SortField>['key']) => void; onRemoveSort: (sortId: SelectedSortType<SortField>['key']) => void;
onCancelClick: () => void; onCancelClick: () => void;
hasFilterButton?: boolean;
}; };
const StyledBar = styled.div` const StyledBar = styled.div`
@ -37,6 +41,7 @@ const StyledChipcontainer = styled.div`
height: 40px; height: 40px;
justify-content: space-between; justify-content: space-between;
margin-left: ${({ theme }) => theme.spacing(2)}; margin-left: ${({ theme }) => theme.spacing(2)};
margin-right: ${({ theme }) => theme.spacing(1)};
overflow-x: auto; overflow-x: auto;
`; `;
@ -61,11 +66,17 @@ const StyledCancelButton = styled.button`
} }
`; `;
const StyledFilterContainer = styled.div`
align-items: center;
display: flex;
`;
function SortAndFilterBar<SortField>({ function SortAndFilterBar<SortField>({
context, context,
sorts, sorts,
onRemoveSort, onRemoveSort,
onCancelClick, onCancelClick,
hasFilterButton = false,
}: OwnProps<SortField>) { }: OwnProps<SortField>) {
const theme = useTheme(); const theme = useTheme();
@ -79,6 +90,11 @@ function SortAndFilterBar<SortField>({
context, context,
); );
const [isSortAndFilterBarOpen] = useRecoilScopedState(
sortAndFilterBarScopedState,
context,
);
const filtersWithDefinition = filters.map((filter) => { const filtersWithDefinition = filters.map((filter) => {
const filterDefinition = availableFilters.find((availableFilter) => { const filterDefinition = availableFilters.find((availableFilter) => {
return availableFilter.field === filter.field; return availableFilter.field === filter.field;
@ -97,47 +113,60 @@ function SortAndFilterBar<SortField>({
onCancelClick(); onCancelClick();
} }
if (!filtersWithDefinition.length && !sorts.length) { if (
(!filtersWithDefinition.length && !sorts.length) ||
!isSortAndFilterBarOpen
) {
return null; return null;
} }
return ( return (
<StyledBar> <StyledBar>
<StyledChipcontainer> <StyledFilterContainer>
{sorts.map((sort) => { <StyledChipcontainer>
return ( {sorts.map((sort) => {
<SortOrFilterChip return (
key={sort.key} <SortOrFilterChip
labelValue={sort.label} key={sort.key}
id={sort.key} labelValue={sort.label}
icon={ id={sort.key}
sort.order === 'desc' ? ( icon={
<IconArrowNarrowDown size={theme.icon.size.md} /> sort.order === 'desc' ? (
) : ( <IconArrowNarrowDown size={theme.icon.size.md} />
<IconArrowNarrowUp size={theme.icon.size.md} /> ) : (
) <IconArrowNarrowUp size={theme.icon.size.md} />
} )
onRemove={() => onRemoveSort(sort.key)} }
/> onRemove={() => onRemoveSort(sort.key)}
); />
})} );
{filtersWithDefinition.map((filter) => { })}
return ( {filtersWithDefinition.map((filter) => {
<SortOrFilterChip return (
key={filter.field} <SortOrFilterChip
labelKey={filter.label} key={filter.field}
labelValue={`${getOperandLabelShort(filter.operand)} ${ labelKey={filter.label}
filter.displayValue labelValue={`${getOperandLabelShort(filter.operand)} ${
}`} filter.displayValue
id={filter.field} }`}
icon={filter.icon} id={filter.field}
onRemove={() => { icon={filter.icon}
removeFilter(filter.field); onRemove={() => {
}} removeFilter(filter.field);
/> }}
); />
})} );
</StyledChipcontainer> })}
</StyledChipcontainer>
{hasFilterButton && (
<FilterDropdownButton
context={context}
HotkeyScope={FiltersHotkeyScope.FilterDropdownButton}
color={theme.font.color.secondary}
label={`+ Add filter`}
/>
)}
</StyledFilterContainer>
{filters.length + sorts.length > 0 && ( {filters.length + sorts.length > 0 && (
<StyledCancelButton <StyledCancelButton
data-testid={'cancel-button'} data-testid={'cancel-button'}

View File

@ -1,4 +1,4 @@
import { useCallback, useState } from 'react'; import { Context, useCallback, useEffect, useState } from 'react';
import { useTheme } from '@emotion/react'; import { useTheme } from '@emotion/react';
import { IconChevronDown } from '@tabler/icons-react'; import { IconChevronDown } from '@tabler/icons-react';
@ -7,7 +7,9 @@ import { DropdownMenuItemsContainer } from '@/ui/dropdown/components/DropdownMen
import { DropdownMenuSelectableItem } from '@/ui/dropdown/components/DropdownMenuSelectableItem'; import { DropdownMenuSelectableItem } from '@/ui/dropdown/components/DropdownMenuSelectableItem';
import { DropdownMenuSeparator } from '@/ui/dropdown/components/DropdownMenuSeparator'; import { DropdownMenuSeparator } from '@/ui/dropdown/components/DropdownMenuSeparator';
import { OverflowingTextWithTooltip } from '@/ui/tooltip/OverflowingTextWithTooltip'; import { OverflowingTextWithTooltip } from '@/ui/tooltip/OverflowingTextWithTooltip';
import { useRecoilScopedState } from '@/ui/utilities/recoil-scope/hooks/useRecoilScopedState';
import { sortAndFilterBarScopedState } from '../states/sortAndFilterBarScopedState';
import { FiltersHotkeyScope } from '../types/FiltersHotkeyScope'; import { FiltersHotkeyScope } from '../types/FiltersHotkeyScope';
import { SelectedSortType, SortType } from '../types/interface'; import { SelectedSortType, SortType } from '../types/interface';
@ -18,6 +20,8 @@ type OwnProps<SortField> = {
onSortSelect: (sort: SelectedSortType<SortField>) => void; onSortSelect: (sort: SelectedSortType<SortField>) => void;
availableSorts: SortType<SortField>[]; availableSorts: SortType<SortField>[];
HotkeyScope: FiltersHotkeyScope; HotkeyScope: FiltersHotkeyScope;
context: Context<string | null>;
isPrimaryButton?: boolean;
}; };
const options: Array<SelectedSortType<any>['order']> = ['asc', 'desc']; const options: Array<SelectedSortType<any>['order']> = ['asc', 'desc'];
@ -27,6 +31,8 @@ export function SortDropdownButton<SortField>({
availableSorts, availableSorts,
onSortSelect, onSortSelect,
HotkeyScope, HotkeyScope,
context,
isPrimaryButton,
}: OwnProps<SortField>) { }: OwnProps<SortField>) {
const theme = useTheme(); const theme = useTheme();
@ -47,6 +53,9 @@ export function SortDropdownButton<SortField>({
setSelectedSortDirection('asc'); setSelectedSortDirection('asc');
}, []); }, []);
const [isSortAndFilterBarOpen, setIsSortAndFilterBarOpen] =
useRecoilScopedState(sortAndFilterBarScopedState, context);
function handleIsUnfoldedChange(newIsUnfolded: boolean) { function handleIsUnfoldedChange(newIsUnfolded: boolean) {
if (newIsUnfolded) { if (newIsUnfolded) {
setIsUnfolded(true); setIsUnfolded(true);
@ -56,6 +65,18 @@ export function SortDropdownButton<SortField>({
} }
} }
useEffect(() => {
if (isSortAndFilterBarOpen && isPrimaryButton && isSortSelected) {
setIsUnfolded(true);
}
}, [isPrimaryButton, isSortAndFilterBarOpen, isSortSelected]);
function handleAddSort(sort: SortType<SortField>) {
setIsUnfolded(false);
onSortItemSelect(sort);
setIsSortAndFilterBarOpen(true);
}
return ( return (
<DropdownButton <DropdownButton
label="Sort" label="Sort"
@ -92,10 +113,7 @@ export function SortDropdownButton<SortField>({
{availableSorts.map((sort, index) => ( {availableSorts.map((sort, index) => (
<DropdownMenuSelectableItem <DropdownMenuSelectableItem
key={index} key={index}
onClick={() => { onClick={() => handleAddSort(sort)}
setIsUnfolded(false);
onSortItemSelect(sort);
}}
> >
{sort.icon} {sort.icon}
<OverflowingTextWithTooltip text={sort.label} /> <OverflowingTextWithTooltip text={sort.label} />

View File

@ -0,0 +1,6 @@
import { atomFamily } from 'recoil';
export const sortAndFilterBarScopedState = atomFamily<boolean, string>({
key: 'sortAndFilterBarScopedState',
default: false,
});

View File

@ -78,12 +78,15 @@ export function TableHeader<SortField>({
<FilterDropdownButton <FilterDropdownButton
context={TableRecoilScopeContext} context={TableRecoilScopeContext}
HotkeyScope={FiltersHotkeyScope.FilterDropdownButton} HotkeyScope={FiltersHotkeyScope.FilterDropdownButton}
isPrimaryButton
/> />
<SortDropdownButton<SortField> <SortDropdownButton<SortField>
context={TableContext}
isSortSelected={sorts.length > 0} isSortSelected={sorts.length > 0}
availableSorts={availableSorts || []} availableSorts={availableSorts || []}
onSortSelect={sortSelect} onSortSelect={sortSelect}
HotkeyScope={FiltersHotkeyScope.FilterDropdownButton} HotkeyScope={FiltersHotkeyScope.FilterDropdownButton}
isPrimaryButton
/> />
<TableOptionsDropdownButton <TableOptionsDropdownButton
onColumnsChange={onColumnsChange} onColumnsChange={onColumnsChange}
@ -99,6 +102,7 @@ export function TableHeader<SortField>({
onCancelClick={() => { onCancelClick={() => {
handleSortsUpdate([]); handleSortsUpdate([]);
}} }}
hasFilterButton
/> />
} }
/> />