chore: filters view more and less buttons (#2583)

Co-authored-by: sriram veeraghanta <veeraghanta.sriram@gmail.com>
This commit is contained in:
Aaryan Khandelwal 2023-11-01 20:34:02 +05:30 committed by GitHub
parent 36152ea2fa
commit d63e7cf254
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 142 additions and 249 deletions

View File

@ -9,15 +9,14 @@ import { IUserLite } from "types";
type Props = {
appliedFilters: string[] | null;
handleUpdate: (val: string) => void;
itemsToRender: number;
members: IUserLite[] | undefined;
searchQuery: string;
viewButtons: React.ReactNode;
};
export const FilterAssignees: React.FC<Props> = (props) => {
const { appliedFilters, handleUpdate, itemsToRender, members, searchQuery, viewButtons } = props;
const { appliedFilters, handleUpdate, members, searchQuery } = props;
const [itemsToRender, setItemsToRender] = useState(5);
const [previewEnabled, setPreviewEnabled] = useState(true);
const appliedFiltersCount = appliedFilters?.length ?? 0;
@ -26,6 +25,13 @@ export const FilterAssignees: React.FC<Props> = (props) => {
member.display_name.toLowerCase().includes(searchQuery.toLowerCase())
);
const handleViewToggle = () => {
if (!filteredOptions) return;
if (itemsToRender === filteredOptions.length) setItemsToRender(5);
else setItemsToRender(filteredOptions.length);
};
return (
<>
<FilterHeader
@ -47,7 +53,15 @@ export const FilterAssignees: React.FC<Props> = (props) => {
title={member.display_name}
/>
))}
{viewButtons}
{filteredOptions.length > 5 && (
<button
type="button"
className="text-custom-primary-100 text-xs font-medium ml-8"
onClick={handleViewToggle}
>
{itemsToRender === filteredOptions.length ? "View less" : "View all"}
</button>
)}
</>
) : (
<p className="text-xs text-custom-text-400 italic">No matches found</p>

View File

@ -9,15 +9,14 @@ import { IUserLite } from "types";
type Props = {
appliedFilters: string[] | null;
handleUpdate: (val: string) => void;
itemsToRender: number;
members: IUserLite[] | undefined;
searchQuery: string;
viewButtons: React.ReactNode;
};
export const FilterCreatedBy: React.FC<Props> = (props) => {
const { appliedFilters, handleUpdate, itemsToRender, members, searchQuery, viewButtons } = props;
const { appliedFilters, handleUpdate, members, searchQuery } = props;
const [itemsToRender, setItemsToRender] = useState(5);
const [previewEnabled, setPreviewEnabled] = useState(true);
const appliedFiltersCount = appliedFilters?.length ?? 0;
@ -26,6 +25,13 @@ export const FilterCreatedBy: React.FC<Props> = (props) => {
member.display_name.toLowerCase().includes(searchQuery.toLowerCase())
);
const handleViewToggle = () => {
if (!filteredOptions) return;
if (itemsToRender === filteredOptions.length) setItemsToRender(5);
else setItemsToRender(filteredOptions.length);
};
return (
<>
<FilterHeader
@ -47,7 +53,15 @@ export const FilterCreatedBy: React.FC<Props> = (props) => {
title={member.display_name}
/>
))}
{viewButtons}
{filteredOptions.length > 5 && (
<button
type="button"
className="text-custom-primary-100 text-xs font-medium ml-8"
onClick={handleViewToggle}
>
{itemsToRender === filteredOptions.length ? "View less" : "View all"}
</button>
)}
</>
) : (
<p className="text-xs text-custom-text-400 italic">No matches found</p>

View File

@ -1,6 +1,6 @@
import React, { useState } from "react";
import { observer } from "mobx-react-lite";
import { Search, X } from "lucide-react";
// components
import {
FilterAssignees,
@ -14,15 +14,10 @@ import {
FilterStateGroup,
FilterTargetDate,
} from "components/issues";
// icons
import { Search, X } from "lucide-react";
// helpers
import { getStatesList } from "helpers/state.helper";
// types
import { IIssueFilterOptions, IIssueLabels, IProject, IStateResponse, IUserLite } from "types";
// constants
import { ILayoutDisplayFiltersOptions, ISSUE_PRIORITIES, ISSUE_STATE_GROUPS } from "constants/issue";
import { DATE_FILTER_OPTIONS } from "constants/filters";
import { ILayoutDisplayFiltersOptions } from "constants/issue";
type Props = {
filters: IIssueFilterOptions;
@ -34,129 +29,11 @@ type Props = {
states?: IStateResponse | undefined;
};
type ViewButtonProps = {
handleLess: () => void;
handleMore: () => void;
isViewLessVisible: boolean;
isViewMoreVisible: boolean;
};
const ViewButtons = ({ handleLess, handleMore, isViewLessVisible, isViewMoreVisible }: ViewButtonProps) => (
<div className="flex items-center gap-2 ml-7 mt-1">
{/* TODO: handle view more and less in a better way */}
{isViewMoreVisible && (
<button className="text-custom-primary-100 text-xs font-medium" onClick={handleMore}>
View more
</button>
)}
{isViewLessVisible && (
<button className="text-custom-primary-100 text-xs font-medium" onClick={handleLess}>
View less
</button>
)}
</div>
);
export const FilterSelection: React.FC<Props> = observer((props) => {
const { filters, handleFiltersUpdate, layoutDisplayFiltersOptions, labels, members, projects, states } = props;
const [filtersSearchQuery, setFiltersSearchQuery] = useState("");
const statesList = getStatesList(states);
const [filtersToRender, setFiltersToRender] = useState<{
[key in keyof IIssueFilterOptions]: {
currentLength: number;
totalLength: number;
};
}>({
assignees: {
currentLength: 5,
totalLength: members?.length ?? 0,
},
mentions: {
currentLength: 5,
totalLength: members?.length ?? 0,
},
created_by: {
currentLength: 5,
totalLength: members?.length ?? 0,
},
labels: {
currentLength: 5,
totalLength: labels?.length ?? 0,
},
priority: {
currentLength: 5,
totalLength: ISSUE_PRIORITIES.length,
},
project: {
currentLength: 5,
totalLength: projects?.length ?? 0,
},
state_group: {
currentLength: 5,
totalLength: ISSUE_STATE_GROUPS.length,
},
state: {
currentLength: 5,
totalLength: statesList?.length ?? 0,
},
start_date: {
currentLength: 5,
totalLength: DATE_FILTER_OPTIONS.length + 1,
},
target_date: {
currentLength: 5,
totalLength: DATE_FILTER_OPTIONS.length + 1,
},
});
const handleViewMore = (filterName: keyof IIssueFilterOptions) => {
const filterDetails = filtersToRender[filterName];
if (!filterDetails) return;
if (filterDetails.currentLength <= filterDetails.totalLength)
setFiltersToRender((prev) => ({
...prev,
[filterName]: {
...prev[filterName],
currentLength: filterDetails.currentLength + 5,
},
}));
};
const handleViewLess = (filterName: keyof IIssueFilterOptions) => {
const filterDetails = filtersToRender[filterName];
if (!filterDetails) return;
setFiltersToRender((prev) => ({
...prev,
[filterName]: {
...prev[filterName],
currentLength: 5,
},
}));
};
const isViewMoreVisible = (filterName: keyof IIssueFilterOptions): boolean => {
const filterDetails = filtersToRender[filterName];
if (!filterDetails) return false;
return filterDetails.currentLength < filterDetails.totalLength;
};
const isViewLessVisible = (filterName: keyof IIssueFilterOptions): boolean => {
const filterDetails = filtersToRender[filterName];
if (!filterDetails) return false;
return filterDetails.currentLength > 5;
};
const isFilterEnabled = (filter: keyof IIssueFilterOptions) => layoutDisplayFiltersOptions?.filters.includes(filter);
return (
@ -186,16 +63,7 @@ export const FilterSelection: React.FC<Props> = observer((props) => {
<FilterPriority
appliedFilters={filters.priority ?? null}
handleUpdate={(val) => handleFiltersUpdate("priority", val)}
itemsToRender={filtersToRender.priority?.currentLength ?? 0}
searchQuery={filtersSearchQuery}
viewButtons={
<ViewButtons
isViewLessVisible={isViewLessVisible("priority")}
isViewMoreVisible={isViewMoreVisible("priority")}
handleLess={() => handleViewLess("priority")}
handleMore={() => handleViewMore("priority")}
/>
}
/>
</div>
)}
@ -206,16 +74,7 @@ export const FilterSelection: React.FC<Props> = observer((props) => {
<FilterStateGroup
appliedFilters={filters.state_group ?? null}
handleUpdate={(val) => handleFiltersUpdate("state_group", val)}
itemsToRender={filtersToRender.state_group?.currentLength ?? 0}
searchQuery={filtersSearchQuery}
viewButtons={
<ViewButtons
isViewLessVisible={isViewLessVisible("state_group")}
isViewMoreVisible={isViewMoreVisible("state_group")}
handleLess={() => handleViewLess("state_group")}
handleMore={() => handleViewMore("state_group")}
/>
}
/>
</div>
)}
@ -226,17 +85,8 @@ export const FilterSelection: React.FC<Props> = observer((props) => {
<FilterState
appliedFilters={filters.state ?? null}
handleUpdate={(val) => handleFiltersUpdate("state", val)}
itemsToRender={filtersToRender.state?.currentLength ?? 0}
searchQuery={filtersSearchQuery}
states={states}
viewButtons={
<ViewButtons
isViewLessVisible={isViewLessVisible("state")}
isViewMoreVisible={isViewMoreVisible("state")}
handleLess={() => handleViewLess("state")}
handleMore={() => handleViewMore("state")}
/>
}
/>
</div>
)}
@ -247,17 +97,8 @@ export const FilterSelection: React.FC<Props> = observer((props) => {
<FilterAssignees
appliedFilters={filters.assignees ?? null}
handleUpdate={(val) => handleFiltersUpdate("assignees", val)}
itemsToRender={filtersToRender.assignees?.currentLength ?? 0}
members={members}
searchQuery={filtersSearchQuery}
viewButtons={
<ViewButtons
isViewLessVisible={isViewLessVisible("assignees")}
isViewMoreVisible={isViewMoreVisible("assignees")}
handleLess={() => handleViewLess("assignees")}
handleMore={() => handleViewMore("assignees")}
/>
}
/>
</div>
)}
@ -268,17 +109,8 @@ export const FilterSelection: React.FC<Props> = observer((props) => {
<FilterMentions
appliedFilters={filters.mentions ?? null}
handleUpdate={(val) => handleFiltersUpdate("mentions", val)}
itemsToRender={filtersToRender.mentions?.currentLength ?? 0}
members={members}
searchQuery={filtersSearchQuery}
viewButtons={
<ViewButtons
isViewLessVisible={isViewLessVisible("mentions")}
isViewMoreVisible={isViewMoreVisible("mentions")}
handleLess={() => handleViewLess("mentions")}
handleMore={() => handleViewMore("mentions")}
/>
}
/>
</div>
)}
@ -289,17 +121,8 @@ export const FilterSelection: React.FC<Props> = observer((props) => {
<FilterCreatedBy
appliedFilters={filters.created_by ?? null}
handleUpdate={(val) => handleFiltersUpdate("created_by", val)}
itemsToRender={filtersToRender.created_by?.currentLength ?? 0}
members={members}
searchQuery={filtersSearchQuery}
viewButtons={
<ViewButtons
isViewLessVisible={isViewLessVisible("created_by")}
isViewMoreVisible={isViewMoreVisible("created_by")}
handleLess={() => handleViewLess("created_by")}
handleMore={() => handleViewMore("created_by")}
/>
}
/>
</div>
)}
@ -310,17 +133,8 @@ export const FilterSelection: React.FC<Props> = observer((props) => {
<FilterLabels
appliedFilters={filters.labels ?? null}
handleUpdate={(val) => handleFiltersUpdate("labels", val)}
itemsToRender={filtersToRender.labels?.currentLength ?? 0}
labels={labels}
searchQuery={filtersSearchQuery}
viewButtons={
<ViewButtons
isViewLessVisible={isViewLessVisible("labels")}
isViewMoreVisible={isViewMoreVisible("labels")}
handleLess={() => handleViewLess("labels")}
handleMore={() => handleViewMore("labels")}
/>
}
/>
</div>
)}
@ -332,16 +146,7 @@ export const FilterSelection: React.FC<Props> = observer((props) => {
appliedFilters={filters.project ?? null}
projects={projects}
handleUpdate={(val) => handleFiltersUpdate("project", val)}
itemsToRender={filtersToRender.project?.currentLength ?? 0}
searchQuery={filtersSearchQuery}
viewButtons={
<ViewButtons
isViewLessVisible={isViewLessVisible("project")}
isViewMoreVisible={isViewMoreVisible("project")}
handleLess={() => handleViewLess("project")}
handleMore={() => handleViewMore("project")}
/>
}
/>
</div>
)}
@ -352,7 +157,6 @@ export const FilterSelection: React.FC<Props> = observer((props) => {
<FilterStartDate
appliedFilters={filters.start_date ?? null}
handleUpdate={(val) => handleFiltersUpdate("start_date", val)}
itemsToRender={filtersToRender.start_date?.currentLength ?? 0}
searchQuery={filtersSearchQuery}
/>
</div>
@ -364,7 +168,6 @@ export const FilterSelection: React.FC<Props> = observer((props) => {
<FilterTargetDate
appliedFilters={filters.target_date ?? null}
handleUpdate={(val) => handleFiltersUpdate("target_date", val)}
itemsToRender={filtersToRender.target_date?.currentLength ?? 0}
searchQuery={filtersSearchQuery}
/>
</div>

View File

@ -14,21 +14,27 @@ const LabelIcons = ({ color }: { color: string }) => (
type Props = {
appliedFilters: string[] | null;
handleUpdate: (val: string) => void;
itemsToRender: number;
labels: IIssueLabels[] | undefined;
searchQuery: string;
viewButtons: React.ReactNode;
};
export const FilterLabels: React.FC<Props> = (props) => {
const { appliedFilters, handleUpdate, itemsToRender, labels, searchQuery, viewButtons } = props;
const { appliedFilters, handleUpdate, labels, searchQuery } = props;
const [itemsToRender, setItemsToRender] = useState(5);
const [previewEnabled, setPreviewEnabled] = useState(true);
const appliedFiltersCount = appliedFilters?.length ?? 0;
const filteredOptions = labels?.filter((label) => label.name.toLowerCase().includes(searchQuery.toLowerCase()));
const handleViewToggle = () => {
if (!filteredOptions) return;
if (itemsToRender === filteredOptions.length) setItemsToRender(5);
else setItemsToRender(filteredOptions.length);
};
return (
<>
<FilterHeader
@ -50,7 +56,15 @@ export const FilterLabels: React.FC<Props> = (props) => {
title={label.name}
/>
))}
{viewButtons}
{filteredOptions.length > 5 && (
<button
type="button"
className="text-custom-primary-100 text-xs font-medium ml-8"
onClick={handleViewToggle}
>
{itemsToRender === filteredOptions.length ? "View less" : "View all"}
</button>
)}
</>
) : (
<p className="text-xs text-custom-text-400 italic">No matches found</p>

View File

@ -1,5 +1,4 @@
import React, { useState } from "react";
// components
import { FilterHeader, FilterOption } from "components/issues";
// ui
@ -10,15 +9,14 @@ import { IUserLite } from "types";
type Props = {
appliedFilters: string[] | null;
handleUpdate: (val: string) => void;
itemsToRender: number;
members: IUserLite[] | undefined;
searchQuery: string;
viewButtons: React.ReactNode;
};
export const FilterMentions: React.FC<Props> = (props) => {
const { appliedFilters, handleUpdate, itemsToRender, members, searchQuery, viewButtons } = props;
const { appliedFilters, handleUpdate, members, searchQuery } = props;
const [itemsToRender, setItemsToRender] = useState(5);
const [previewEnabled, setPreviewEnabled] = useState(true);
const appliedFiltersCount = appliedFilters?.length ?? 0;
@ -27,6 +25,13 @@ export const FilterMentions: React.FC<Props> = (props) => {
member.display_name.toLowerCase().includes(searchQuery.toLowerCase())
);
const handleViewToggle = () => {
if (!filteredOptions) return;
if (itemsToRender === filteredOptions.length) setItemsToRender(5);
else setItemsToRender(filteredOptions.length);
};
return (
<>
<FilterHeader
@ -48,7 +53,15 @@ export const FilterMentions: React.FC<Props> = (props) => {
title={member.display_name}
/>
))}
{viewButtons}
{filteredOptions.length > 5 && (
<button
type="button"
className="text-custom-primary-100 text-xs font-medium ml-8"
onClick={handleViewToggle}
>
{itemsToRender === filteredOptions.length ? "View less" : "View all"}
</button>
)}
</>
) : (
<p className="text-xs text-custom-text-400 italic">No matches found</p>

View File

@ -51,13 +51,11 @@ const PriorityIcons = ({
type Props = {
appliedFilters: string[] | null;
handleUpdate: (val: string) => void;
itemsToRender: number;
searchQuery: string;
viewButtons: React.ReactNode;
};
export const FilterPriority: React.FC<Props> = observer((props) => {
const { appliedFilters, handleUpdate, itemsToRender, searchQuery, viewButtons } = props;
const { appliedFilters, handleUpdate, searchQuery } = props;
const [previewEnabled, setPreviewEnabled] = useState(true);
@ -75,18 +73,15 @@ export const FilterPriority: React.FC<Props> = observer((props) => {
{previewEnabled && (
<div>
{filteredOptions.length > 0 ? (
<>
{filteredOptions.slice(0, itemsToRender).map((priority) => (
<FilterOption
key={priority.key}
isChecked={appliedFilters?.includes(priority.key) ? true : false}
onClick={() => handleUpdate(priority.key)}
icon={<PriorityIcons priority={priority.key} />}
title={priority.title}
/>
))}
{viewButtons}
</>
filteredOptions.map((priority) => (
<FilterOption
key={priority.key}
isChecked={appliedFilters?.includes(priority.key) ? true : false}
onClick={() => handleUpdate(priority.key)}
icon={<PriorityIcons priority={priority.key} />}
title={priority.title}
/>
))
) : (
<p className="text-xs text-custom-text-400 italic">No matches found</p>
)}

View File

@ -12,21 +12,27 @@ import { IProject } from "types";
type Props = {
appliedFilters: string[] | null;
handleUpdate: (val: string) => void;
itemsToRender: number;
projects: IProject[] | undefined;
searchQuery: string;
viewButtons: React.ReactNode;
};
export const FilterProjects: React.FC<Props> = (props) => {
const { appliedFilters, handleUpdate, itemsToRender, projects, searchQuery, viewButtons } = props;
const { appliedFilters, handleUpdate, projects, searchQuery } = props;
const [itemsToRender, setItemsToRender] = useState(5);
const [previewEnabled, setPreviewEnabled] = useState(true);
const appliedFiltersCount = appliedFilters?.length ?? 0;
const filteredOptions = projects?.filter((project) => project.name.toLowerCase().includes(searchQuery.toLowerCase()));
const handleViewToggle = () => {
if (!filteredOptions) return;
if (itemsToRender === filteredOptions.length) setItemsToRender(5);
else setItemsToRender(filteredOptions.length);
};
return (
<>
<FilterHeader
@ -62,7 +68,15 @@ export const FilterProjects: React.FC<Props> = (props) => {
title={project.name}
/>
))}
{viewButtons}
{filteredOptions.length > 5 && (
<button
type="button"
className="text-custom-primary-100 text-xs font-medium ml-8"
onClick={handleViewToggle}
>
{itemsToRender === filteredOptions.length ? "View less" : "View all"}
</button>
)}
</>
) : (
<p className="text-xs text-custom-text-400 italic">No matches found</p>

View File

@ -10,12 +10,11 @@ import { DATE_FILTER_OPTIONS } from "constants/filters";
type Props = {
appliedFilters: string[] | null;
handleUpdate: (val: string | string[]) => void;
itemsToRender: number;
searchQuery: string;
};
export const FilterStartDate: React.FC<Props> = observer((props) => {
const { appliedFilters, handleUpdate, itemsToRender, searchQuery } = props;
const { appliedFilters, handleUpdate, searchQuery } = props;
const [previewEnabled, setPreviewEnabled] = useState(true);
const [isDateFilterModalOpen, setIsDateFilterModalOpen] = useState(false);
@ -43,7 +42,7 @@ export const FilterStartDate: React.FC<Props> = observer((props) => {
<div>
{filteredOptions.length > 0 ? (
<>
{filteredOptions.slice(0, itemsToRender).map((option) => (
{filteredOptions.map((option) => (
<FilterOption
key={option.value}
isChecked={appliedFilters?.includes(option.value) ? true : false}

View File

@ -11,20 +11,26 @@ import { ISSUE_STATE_GROUPS } from "constants/issue";
type Props = {
appliedFilters: string[] | null;
handleUpdate: (val: string) => void;
itemsToRender: number;
searchQuery: string;
viewButtons: React.ReactNode;
};
export const FilterStateGroup: React.FC<Props> = observer((props) => {
const { appliedFilters, handleUpdate, itemsToRender, searchQuery, viewButtons } = props;
const { appliedFilters, handleUpdate, searchQuery } = props;
const [itemsToRender, setItemsToRender] = useState(5);
const [previewEnabled, setPreviewEnabled] = useState(true);
const appliedFiltersCount = appliedFilters?.length ?? 0;
const filteredOptions = ISSUE_STATE_GROUPS.filter((s) => s.key.includes(searchQuery.toLowerCase()));
const handleViewToggle = () => {
if (!filteredOptions) return;
if (itemsToRender === filteredOptions.length) setItemsToRender(5);
else setItemsToRender(filteredOptions.length);
};
return (
<>
<FilterHeader
@ -45,7 +51,15 @@ export const FilterStateGroup: React.FC<Props> = observer((props) => {
title={stateGroup.title}
/>
))}
{viewButtons}
{filteredOptions.length > 5 && (
<button
type="button"
className="text-custom-primary-100 text-xs font-medium ml-8"
onClick={handleViewToggle}
>
{itemsToRender === filteredOptions.length ? "View less" : "View all"}
</button>
)}
</>
) : (
<p className="text-xs text-custom-text-400 italic">No matches found</p>

View File

@ -12,15 +12,14 @@ import { IStateResponse } from "types";
type Props = {
appliedFilters: string[] | null;
handleUpdate: (val: string) => void;
itemsToRender: number;
searchQuery: string;
states: IStateResponse | undefined;
viewButtons: React.ReactNode;
};
export const FilterState: React.FC<Props> = (props) => {
const { appliedFilters, handleUpdate, itemsToRender, searchQuery, states, viewButtons } = props;
const { appliedFilters, handleUpdate, searchQuery, states } = props;
const [itemsToRender, setItemsToRender] = useState(5);
const [previewEnabled, setPreviewEnabled] = useState(true);
const statesList = getStatesList(states);
@ -29,6 +28,13 @@ export const FilterState: React.FC<Props> = (props) => {
const filteredOptions = statesList?.filter((s) => s.name.toLowerCase().includes(searchQuery.toLowerCase()));
const handleViewToggle = () => {
if (!filteredOptions) return;
if (itemsToRender === filteredOptions.length) setItemsToRender(5);
else setItemsToRender(filteredOptions.length);
};
return (
<>
<FilterHeader
@ -50,7 +56,15 @@ export const FilterState: React.FC<Props> = (props) => {
title={state.name}
/>
))}
{viewButtons}
{filteredOptions.length > 5 && (
<button
type="button"
className="text-custom-primary-100 text-xs font-medium ml-8"
onClick={handleViewToggle}
>
{itemsToRender === filteredOptions.length ? "View less" : "View all"}
</button>
)}
</>
) : (
<p className="text-xs text-custom-text-400 italic">No matches found</p>

View File

@ -10,12 +10,11 @@ import { DATE_FILTER_OPTIONS } from "constants/filters";
type Props = {
appliedFilters: string[] | null;
handleUpdate: (val: string | string[]) => void;
itemsToRender: number;
searchQuery: string;
};
export const FilterTargetDate: React.FC<Props> = observer((props) => {
const { appliedFilters, handleUpdate, itemsToRender, searchQuery } = props;
const { appliedFilters, handleUpdate, searchQuery } = props;
const [previewEnabled, setPreviewEnabled] = useState(true);
const [isDateFilterModalOpen, setIsDateFilterModalOpen] = useState(false);
@ -43,7 +42,7 @@ export const FilterTargetDate: React.FC<Props> = observer((props) => {
<div>
{filteredOptions.length > 0 ? (
<>
{filteredOptions.slice(0, itemsToRender).map((option) => (
{filteredOptions.map((option) => (
<FilterOption
key={option.value}
isChecked={appliedFilters?.includes(option.value) ? true : false}