style: filter issues dropdown (#466)

This commit is contained in:
Aaryan Khandelwal 2023-03-16 18:14:07 +05:30 committed by GitHub
parent 0f06589b83
commit 23c468786d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 115 additions and 59 deletions

View File

@ -13,21 +13,24 @@ import useIssuesProperties from "hooks/use-issue-properties";
import useIssuesView from "hooks/use-issues-view"; import useIssuesView from "hooks/use-issues-view";
// headless ui // headless ui
import { Popover, Transition } from "@headlessui/react"; import { Popover, Transition } from "@headlessui/react";
// components
import { PRIORITIES } from "constants/project";
// ui // ui
import { CustomMenu, MultiLevelDropdown } from "components/ui"; import { Avatar, CustomMenu, MultiLevelDropdown } from "components/ui";
// icons // icons
import { ChevronDownIcon, ListBulletIcon } from "@heroicons/react/24/outline"; import { ChevronDownIcon, ListBulletIcon } from "@heroicons/react/24/outline";
import { Squares2X2Icon } from "@heroicons/react/20/solid"; import { Squares2X2Icon } from "@heroicons/react/20/solid";
import { getStateGroupIcon } from "components/icons";
import { getPriorityIcon } from "components/icons/priority-icon";
// helpers // helpers
import { replaceUnderscoreIfSnakeCase } from "helpers/string.helper"; import { replaceUnderscoreIfSnakeCase } from "helpers/string.helper";
import { getStatesList } from "helpers/state.helper"; import { getStatesList } from "helpers/state.helper";
// types // types
import { IIssue, IIssueLabels, Properties } from "types"; import { IIssueLabels, Properties } from "types";
// fetch-keys // fetch-keys
import { PROJECT_ISSUE_LABELS, PROJECT_MEMBERS, STATE_LIST } from "constants/fetch-keys"; import { PROJECT_ISSUE_LABELS, PROJECT_MEMBERS, STATE_LIST } from "constants/fetch-keys";
// constants // constants
import { GROUP_BY_OPTIONS, ORDER_BY_OPTIONS, FILTER_ISSUE_OPTIONS } from "constants/issue"; import { GROUP_BY_OPTIONS, ORDER_BY_OPTIONS, FILTER_ISSUE_OPTIONS } from "constants/issue";
import { PRIORITIES } from "constants/project";
export const IssuesFilterView: React.FC = () => { export const IssuesFilterView: React.FC = () => {
const router = useRouter(); const router = useRouter();
@ -97,7 +100,7 @@ export const IssuesFilterView: React.FC = () => {
</button> </button>
</div> </div>
<MultiLevelDropdown <MultiLevelDropdown
label="Filter" label="Filters"
onSelect={(option) => { onSelect={(option) => {
setFilters({ setFilters({
...filters, ...filters,
@ -116,7 +119,11 @@ export const IssuesFilterView: React.FC = () => {
children: [ children: [
...PRIORITIES.map((priority) => ({ ...PRIORITIES.map((priority) => ({
id: priority ?? "none", id: priority ?? "none",
label: priority ?? "None", label: (
<div className="flex items-center gap-2">
{getPriorityIcon(priority)} {priority ?? "None"}
</div>
),
value: { value: {
key: "priority", key: "priority",
value: priority, value: priority,
@ -132,7 +139,11 @@ export const IssuesFilterView: React.FC = () => {
children: [ children: [
...statesList.map((state) => ({ ...statesList.map((state) => ({
id: state.id, id: state.id,
label: state.name, label: (
<div className="flex items-center gap-2">
{getStateGroupIcon(state.group, "16", "16", state.color)} {state.name}
</div>
),
value: { value: {
key: "state", key: "state",
value: state.id, value: state.id,
@ -148,7 +159,14 @@ export const IssuesFilterView: React.FC = () => {
children: [ children: [
...(members?.map((member) => ({ ...(members?.map((member) => ({
id: member.member.id, id: member.member.id,
label: member.member.first_name, label: (
<div className="flex items-center gap-2">
<Avatar user={member.member} />
{member.member.first_name && member.member.first_name !== ""
? member.member.first_name
: member.member.email}
</div>
),
value: { value: {
key: "assignee", key: "assignee",
value: member.member.id, value: member.member.id,
@ -163,12 +181,12 @@ export const IssuesFilterView: React.FC = () => {
{({ open }) => ( {({ open }) => (
<> <>
<Popover.Button <Popover.Button
className={`group flex items-center gap-2 rounded-md border bg-transparent p-2 text-xs font-medium hover:bg-gray-100 hover:text-gray-900 focus:outline-none ${ className={`group flex items-center gap-2 rounded-md border bg-transparent px-3 py-1.5 text-xs hover:bg-gray-100 hover:text-gray-900 focus:outline-none ${
open ? "bg-gray-100 text-gray-900" : "text-gray-500" open ? "bg-gray-100 text-gray-900" : "text-gray-500"
}`} }`}
> >
View View
<ChevronDownIcon className="h-4 w-4" aria-hidden="true" /> <ChevronDownIcon className="h-3 w-3" aria-hidden="true" />
</Popover.Button> </Popover.Button>
<Transition <Transition

View File

@ -42,6 +42,7 @@ import {
STATE_LIST, STATE_LIST,
} from "constants/fetch-keys"; } from "constants/fetch-keys";
import { EmptySpace, EmptySpaceItem } from "components/ui"; import { EmptySpace, EmptySpaceItem } from "components/ui";
import { PrimaryButton } from "components/ui/button/primary-button";
type Props = { type Props = {
type?: "issue" | "cycle" | "module"; type?: "issue" | "cycle" | "module";
@ -496,20 +497,21 @@ export const IssuesView: React.FC<Props> = ({ type = "issue", openIssuesListModa
})} })}
</div> </div>
<div> {Object.keys(filters).length > 0 && (
<button <PrimaryButton
type="button" type="button"
onClick={() => onClick={() =>
setCreateViewModal({ setCreateViewModal({
query: filters, query: filters,
}) })
} }
className="flex items-center gap-x-0.5 text-sm" className="flex items-center gap-4 text-sm"
size="sm"
> >
<PlusIcon className="h-3 w-3" /> <PlusIcon className="h-4 w-4" />
<span>Save view</span> Save view
</button> </PrimaryButton>
</div> )}
</div> </div>
<DragDropContext onDragEnd={handleOnDragEnd}> <DragDropContext onDragEnd={handleOnDragEnd}>
<StrictModeDroppable droppableId="trashBox"> <StrictModeDroppable droppableId="trashBox">

View File

@ -18,7 +18,7 @@ export const IssuePrioritySelect: React.FC<Props> = ({ value, onChange }) => (
label={ label={
<div className="flex items-center justify-center gap-2 text-xs"> <div className="flex items-center justify-center gap-2 text-xs">
<span className="flex items-center"> <span className="flex items-center">
{getPriorityIcon(value, `${value ? "text-xs" : "text-xs text-gray-500"}`)} {getPriorityIcon(value, `text-xs ${value ? "" : "text-gray-500"}`)}
</span> </span>
<span className={`${value ? "text-gray-600" : "text-gray-500"} capitalize`}> <span className={`${value ? "text-gray-600" : "text-gray-500"} capitalize`}>
{value ?? "Priority"} {value ?? "Priority"}

View File

@ -136,8 +136,12 @@ export const SingleSidebarProject: React.FC<Props> = ({
className={`${sidebarCollapse ? "" : "ml-[2.25rem]"} flex flex-col gap-y-1`} className={`${sidebarCollapse ? "" : "ml-[2.25rem]"} flex flex-col gap-y-1`}
> >
{navigation(workspaceSlug as string, project?.id).map((item) => { {navigation(workspaceSlug as string, project?.id).map((item) => {
if (item.name === "Cycles" && !project.cycle_view) return; if (
if (item.name === "Modules" && !project.module_view) return; (item.name === "Cycles" && !project.cycle_view) ||
(item.name === "Modules" && !project.module_view) ||
(item.name === "Views" && !project.issue_views_view)
)
return;
return ( return (
<Link key={item.name} href={item.href}> <Link key={item.name} href={item.href}>

View File

@ -1,6 +1,7 @@
import { Menu, Transition } from "@headlessui/react"; import { Menu, Transition } from "@headlessui/react";
import { Fragment, useState } from "react"; import { Fragment, useState } from "react";
import { ChevronDownIcon, ChevronRightIcon, ChevronLeftIcon } from "@heroicons/react/20/solid"; import { ChevronRightIcon, ChevronLeftIcon } from "@heroicons/react/20/solid";
import { ChevronDownIcon } from "@heroicons/react/24/outline";
type MultiLevelDropdownProps = { type MultiLevelDropdownProps = {
label: string; label: string;
@ -11,7 +12,7 @@ type MultiLevelDropdownProps = {
selected?: boolean; selected?: boolean;
children?: { children?: {
id: string; id: string;
label: string; label: string | JSX.Element;
value: any; value: any;
selected?: boolean; selected?: boolean;
}[]; }[];
@ -20,26 +21,27 @@ type MultiLevelDropdownProps = {
direction?: "left" | "right"; direction?: "left" | "right";
}; };
export const MultiLevelDropdown: React.FC<MultiLevelDropdownProps> = (props) => { export const MultiLevelDropdown: React.FC<MultiLevelDropdownProps> = ({
const { label, options, onSelect, direction = "right" } = props; label,
options,
onSelect,
direction = "right",
}) => {
const [openChildFor, setOpenChildFor] = useState<string | null>(null); const [openChildFor, setOpenChildFor] = useState<string | null>(null);
return ( return (
<Menu as="div" className="relative inline-block text-left"> <Menu as="div" className="relative z-10 inline-block text-left">
{({ open }) => ( {({ open }) => (
<> <>
<div> <div>
<Menu.Button <Menu.Button
onClick={() => { onClick={() => setOpenChildFor(null)}
setOpenChildFor(null); className={`group flex items-center justify-between gap-2 rounded-md border px-3 py-1.5 text-xs shadow-sm duration-300 focus:border-indigo-500 focus:outline-none focus:ring-1 focus:ring-indigo-500 ${
}}
className={`group flex items-center gap-2 rounded-md border bg-transparent p-2 text-xs font-medium hover:bg-gray-100 hover:text-gray-900 focus:outline-none ${
open ? "bg-gray-100 text-gray-900" : "text-gray-500" open ? "bg-gray-100 text-gray-900" : "text-gray-500"
}`} }`}
> >
{label} {label}
<ChevronDownIcon className="h-4 w-4" aria-hidden="true" /> <ChevronDownIcon className="h-3 w-3" aria-hidden="true" />
</Menu.Button> </Menu.Button>
</div> </div>
<Transition <Transition
@ -53,10 +55,10 @@ export const MultiLevelDropdown: React.FC<MultiLevelDropdownProps> = (props) =>
> >
<Menu.Items <Menu.Items
static static
className="absolute right-0 mt-2 w-36 origin-top-right select-none divide-y divide-gray-100 rounded-md bg-white shadow-lg ring-1 ring-black ring-opacity-5 focus:outline-none" className="absolute right-0 mt-1 w-36 origin-top-right select-none rounded-md bg-white text-xs shadow-lg ring-1 ring-black ring-opacity-5 focus:outline-none"
> >
{options.map((option) => ( {options.map((option) => (
<div className="relative px-1 py-1" key={option.id}> <div className="relative p-1" key={option.id}>
<Menu.Item <Menu.Item
as="button" as="button"
onClick={(e: any) => { onClick={(e: any) => {
@ -81,7 +83,9 @@ export const MultiLevelDropdown: React.FC<MultiLevelDropdownProps> = (props) =>
<div <div
className={`${ className={`${
active || option.selected ? "bg-gray-100" : "text-gray-900" active || option.selected ? "bg-gray-100" : "text-gray-900"
} group flex w-full items-center justify-between rounded-md px-2 py-2 text-sm`} } flex items-center gap-1 rounded px-1 py-1.5 ${
direction === "right" ? "justify-between" : ""
}`}
> >
{direction === "left" && option.children && ( {direction === "left" && option.children && (
<ChevronLeftIcon className="h-4 w-4" aria-hidden="true" /> <ChevronLeftIcon className="h-4 w-4" aria-hidden="true" />
@ -97,33 +101,31 @@ export const MultiLevelDropdown: React.FC<MultiLevelDropdownProps> = (props) =>
{option.children && option.id === openChildFor && ( {option.children && option.id === openChildFor && (
<Menu.Items <Menu.Items
static static
className={`absolute top-0 mt-2 w-36 origin-top-right select-none divide-y divide-gray-100 rounded-md bg-white shadow-lg ring-1 ring-black ring-opacity-5 focus:outline-none ${ className={`absolute top-0 w-36 origin-top-right select-none rounded-md bg-white shadow-lg ring-1 ring-black ring-opacity-5 focus:outline-none ${
direction === "left" direction === "left"
? "right-full -translate-x-2" ? "right-full -translate-x-1"
: "left-full translate-x-2" : "left-full translate-x-1"
}`} }`}
> >
{option.children.map((child) => ( <div className="p-1">
<div className="relative px-1 py-1" key={child.id}> {option.children.map((child) => (
<Menu.Item as="div" className="flex items-center justify-between"> <Menu.Item
{({ active }) => ( key={child.id}
<> as="button"
<button type="button"
type="button" onClick={() => {
onClick={() => { onSelect(child.value);
onSelect(child.value); }}
}} className={({ active }) =>
className={`${ `${
active || option.selected ? "bg-gray-100" : "text-gray-900" active || option.selected ? "bg-gray-100" : "text-gray-900"
} group flex w-full items-center rounded-md px-2 py-2 text-sm capitalize`} } flex w-full items-center rounded px-1 py-1.5 capitalize`
> }
{child.label} >
</button> {child.label}
</>
)}
</Menu.Item> </Menu.Item>
</div> ))}
))} </div>
</Menu.Items> </Menu.Items>
)} )}
</div> </div>

View File

@ -20,7 +20,7 @@ import { IProject, UserAuth } from "types";
import type { NextPage, GetServerSidePropsContext } from "next"; import type { NextPage, GetServerSidePropsContext } from "next";
// fetch-keys // fetch-keys
import { PROJECTS_LIST, PROJECT_DETAILS } from "constants/fetch-keys"; import { PROJECTS_LIST, PROJECT_DETAILS } from "constants/fetch-keys";
import { ContrastIcon, PeopleGroupIcon } from "components/icons"; import { ContrastIcon, PeopleGroupIcon, ViewListIcon } from "components/icons";
const FeaturesSettings: NextPage<UserAuth> = (props) => { const FeaturesSettings: NextPage<UserAuth> = (props) => {
const router = useRouter(); const router = useRouter();
@ -93,7 +93,7 @@ const FeaturesSettings: NextPage<UserAuth> = (props) => {
<section className="space-y-8"> <section className="space-y-8">
<h3 className="text-2xl font-semibold">Features</h3> <h3 className="text-2xl font-semibold">Features</h3>
<div className="space-y-5"> <div className="space-y-5">
<div className="flex items-center justify-between gap-x-8 gap-y-2 rounded-[10px] border bg-white p-6"> <div className="flex items-center justify-between gap-x-8 gap-y-2 rounded-[10px] border bg-white p-5">
<div className="flex items-start gap-3"> <div className="flex items-start gap-3">
<ContrastIcon color="#3f76ff" width={28} height={28} className="flex-shrink-0" /> <ContrastIcon color="#3f76ff" width={28} height={28} className="flex-shrink-0" />
<div> <div>
@ -122,7 +122,7 @@ const FeaturesSettings: NextPage<UserAuth> = (props) => {
/> />
</button> </button>
</div> </div>
<div className="flex items-center justify-between gap-x-8 gap-y-2 rounded-[10px] border bg-white p-6"> <div className="flex items-center justify-between gap-x-8 gap-y-2 rounded-[10px] border bg-white p-5">
<div className="flex items-start gap-3"> <div className="flex items-start gap-3">
<PeopleGroupIcon color="#ff6b00" width={28} height={28} className="flex-shrink-0" /> <PeopleGroupIcon color="#ff6b00" width={28} height={28} className="flex-shrink-0" />
<div> <div>
@ -151,6 +151,35 @@ const FeaturesSettings: NextPage<UserAuth> = (props) => {
/> />
</button> </button>
</div> </div>
<div className="flex items-center justify-between gap-x-8 gap-y-2 rounded-[10px] border bg-white p-5">
<div className="flex items-start gap-3">
<ViewListIcon color="#ff6b00" width={28} height={28} className="flex-shrink-0" />
<div>
<h4 className="-mt-1.5 text-xl font-semibold">Views</h4>
<p className="text-gray-500">
Modules are enabled for all the projects in this workspace. Access it from the
navigation bar.
</p>
</div>
</div>
<button
type="button"
className={`relative inline-flex h-6 w-11 flex-shrink-0 cursor-pointer rounded-full border-2 border-transparent transition-colors duration-200 ease-in-out focus:outline-none ${
projectDetails?.issue_views_view ? "bg-green-500" : "bg-gray-200"
}`}
role="switch"
aria-checked={projectDetails?.issue_views_view}
onClick={() => handleSubmit({ issue_views_view: !projectDetails?.issue_views_view })}
>
<span className="sr-only">Use views</span>
<span
aria-hidden="true"
className={`inline-block h-5 w-5 transform rounded-full bg-white shadow ring-0 transition duration-200 ease-in-out ${
projectDetails?.issue_views_view ? "translate-x-5" : "translate-x-0"
}`}
/>
</button>
</div>
</div> </div>
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">
<a href="https://plane.so/" target="_blank" rel="noreferrer"> <a href="https://plane.so/" target="_blank" rel="noreferrer">

View File

@ -11,6 +11,7 @@ export interface IProject {
id: string; id: string;
identifier: string; identifier: string;
is_favorite: boolean; is_favorite: boolean;
issue_views_view: boolean;
module_view: boolean; module_view: boolean;
name: string; name: string;
network: number; network: number;