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:
@ -71,6 +71,7 @@ export function BoardHeader<SortField>({
|
||||
HotkeyScope={FiltersHotkeyScope.FilterDropdownButton}
|
||||
/>
|
||||
<SortDropdownButton<SortField>
|
||||
context={context}
|
||||
isSortSelected={sorts.length > 0}
|
||||
availableSorts={availableSorts || []}
|
||||
onSortSelect={sortSelect}
|
||||
|
||||
@ -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>
|
||||
|
||||
@ -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}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
@ -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} />
|
||||
|
||||
@ -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'}
|
||||
|
||||
@ -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} />
|
||||
|
||||
@ -0,0 +1,6 @@
|
||||
import { atomFamily } from 'recoil';
|
||||
|
||||
export const sortAndFilterBarScopedState = atomFamily<boolean, string>({
|
||||
key: 'sortAndFilterBarScopedState',
|
||||
default: false,
|
||||
});
|
||||
@ -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
|
||||
/>
|
||||
}
|
||||
/>
|
||||
|
||||
Reference in New Issue
Block a user