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}
/>
<SortDropdownButton<SortField>
context={context}
isSortSelected={sorts.length > 0}
availableSorts={availableSorts || []}
onSortSelect={sortSelect}

View File

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

View File

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

View File

@ -8,9 +8,12 @@ import { useRecoilScopedState } from '@/ui/utilities/recoil-scope/hooks/useRecoi
import { useRemoveFilter } from '../hooks/useRemoveFilter';
import { availableFiltersScopedState } from '../states/availableFiltersScopedState';
import { filtersScopedState } from '../states/filtersScopedState';
import { sortAndFilterBarScopedState } from '../states/sortAndFilterBarScopedState';
import { FiltersHotkeyScope } from '../types/FiltersHotkeyScope';
import { SelectedSortType } from '../types/interface';
import { getOperandLabelShort } from '../utils/getOperandLabel';
import { FilterDropdownButton } from './FilterDropdownButton';
import SortOrFilterChip from './SortOrFilterChip';
type OwnProps<SortField> = {
@ -18,6 +21,7 @@ type OwnProps<SortField> = {
sorts: Array<SelectedSortType<SortField>>;
onRemoveSort: (sortId: SelectedSortType<SortField>['key']) => void;
onCancelClick: () => void;
hasFilterButton?: boolean;
};
const StyledBar = styled.div`
@ -37,6 +41,7 @@ const StyledChipcontainer = styled.div`
height: 40px;
justify-content: space-between;
margin-left: ${({ theme }) => theme.spacing(2)};
margin-right: ${({ theme }) => theme.spacing(1)};
overflow-x: auto;
`;
@ -61,11 +66,17 @@ const StyledCancelButton = styled.button`
}
`;
const StyledFilterContainer = styled.div`
align-items: center;
display: flex;
`;
function SortAndFilterBar<SortField>({
context,
sorts,
onRemoveSort,
onCancelClick,
hasFilterButton = false,
}: OwnProps<SortField>) {
const theme = useTheme();
@ -79,6 +90,11 @@ function SortAndFilterBar<SortField>({
context,
);
const [isSortAndFilterBarOpen] = useRecoilScopedState(
sortAndFilterBarScopedState,
context,
);
const filtersWithDefinition = filters.map((filter) => {
const filterDefinition = availableFilters.find((availableFilter) => {
return availableFilter.field === filter.field;
@ -97,47 +113,60 @@ function SortAndFilterBar<SortField>({
onCancelClick();
}
if (!filtersWithDefinition.length && !sorts.length) {
if (
(!filtersWithDefinition.length && !sorts.length) ||
!isSortAndFilterBarOpen
) {
return null;
}
return (
<StyledBar>
<StyledChipcontainer>
{sorts.map((sort) => {
return (
<SortOrFilterChip
key={sort.key}
labelValue={sort.label}
id={sort.key}
icon={
sort.order === 'desc' ? (
<IconArrowNarrowDown size={theme.icon.size.md} />
) : (
<IconArrowNarrowUp size={theme.icon.size.md} />
)
}
onRemove={() => onRemoveSort(sort.key)}
/>
);
})}
{filtersWithDefinition.map((filter) => {
return (
<SortOrFilterChip
key={filter.field}
labelKey={filter.label}
labelValue={`${getOperandLabelShort(filter.operand)} ${
filter.displayValue
}`}
id={filter.field}
icon={filter.icon}
onRemove={() => {
removeFilter(filter.field);
}}
/>
);
})}
</StyledChipcontainer>
<StyledFilterContainer>
<StyledChipcontainer>
{sorts.map((sort) => {
return (
<SortOrFilterChip
key={sort.key}
labelValue={sort.label}
id={sort.key}
icon={
sort.order === 'desc' ? (
<IconArrowNarrowDown size={theme.icon.size.md} />
) : (
<IconArrowNarrowUp size={theme.icon.size.md} />
)
}
onRemove={() => onRemoveSort(sort.key)}
/>
);
})}
{filtersWithDefinition.map((filter) => {
return (
<SortOrFilterChip
key={filter.field}
labelKey={filter.label}
labelValue={`${getOperandLabelShort(filter.operand)} ${
filter.displayValue
}`}
id={filter.field}
icon={filter.icon}
onRemove={() => {
removeFilter(filter.field);
}}
/>
);
})}
</StyledChipcontainer>
{hasFilterButton && (
<FilterDropdownButton
context={context}
HotkeyScope={FiltersHotkeyScope.FilterDropdownButton}
color={theme.font.color.secondary}
label={`+ Add filter`}
/>
)}
</StyledFilterContainer>
{filters.length + sorts.length > 0 && (
<StyledCancelButton
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 { IconChevronDown } from '@tabler/icons-react';
@ -7,7 +7,9 @@ import { DropdownMenuItemsContainer } from '@/ui/dropdown/components/DropdownMen
import { DropdownMenuSelectableItem } from '@/ui/dropdown/components/DropdownMenuSelectableItem';
import { DropdownMenuSeparator } from '@/ui/dropdown/components/DropdownMenuSeparator';
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 { SelectedSortType, SortType } from '../types/interface';
@ -18,6 +20,8 @@ type OwnProps<SortField> = {
onSortSelect: (sort: SelectedSortType<SortField>) => void;
availableSorts: SortType<SortField>[];
HotkeyScope: FiltersHotkeyScope;
context: Context<string | null>;
isPrimaryButton?: boolean;
};
const options: Array<SelectedSortType<any>['order']> = ['asc', 'desc'];
@ -27,6 +31,8 @@ export function SortDropdownButton<SortField>({
availableSorts,
onSortSelect,
HotkeyScope,
context,
isPrimaryButton,
}: OwnProps<SortField>) {
const theme = useTheme();
@ -47,6 +53,9 @@ export function SortDropdownButton<SortField>({
setSelectedSortDirection('asc');
}, []);
const [isSortAndFilterBarOpen, setIsSortAndFilterBarOpen] =
useRecoilScopedState(sortAndFilterBarScopedState, context);
function handleIsUnfoldedChange(newIsUnfolded: boolean) {
if (newIsUnfolded) {
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 (
<DropdownButton
label="Sort"
@ -92,10 +113,7 @@ export function SortDropdownButton<SortField>({
{availableSorts.map((sort, index) => (
<DropdownMenuSelectableItem
key={index}
onClick={() => {
setIsUnfolded(false);
onSortItemSelect(sort);
}}
onClick={() => handleAddSort(sort)}
>
{sort.icon}
<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
context={TableRecoilScopeContext}
HotkeyScope={FiltersHotkeyScope.FilterDropdownButton}
isPrimaryButton
/>
<SortDropdownButton<SortField>
context={TableContext}
isSortSelected={sorts.length > 0}
availableSorts={availableSorts || []}
onSortSelect={sortSelect}
HotkeyScope={FiltersHotkeyScope.FilterDropdownButton}
isPrimaryButton
/>
<TableOptionsDropdownButton
onColumnsChange={onColumnsChange}
@ -99,6 +102,7 @@ export function TableHeader<SortField>({
onCancelClick={() => {
handleSortsUpdate([]);
}}
hasFilterButton
/>
}
/>