mirror of
https://github.com/makeplane/plane
synced 2024-06-14 14:31:34 +00:00
style: filter issues dropdown (#466)
This commit is contained in:
parent
0f06589b83
commit
23c468786d
@ -13,21 +13,24 @@ import useIssuesProperties from "hooks/use-issue-properties";
|
||||
import useIssuesView from "hooks/use-issues-view";
|
||||
// headless ui
|
||||
import { Popover, Transition } from "@headlessui/react";
|
||||
// components
|
||||
import { PRIORITIES } from "constants/project";
|
||||
// ui
|
||||
import { CustomMenu, MultiLevelDropdown } from "components/ui";
|
||||
import { Avatar, CustomMenu, MultiLevelDropdown } from "components/ui";
|
||||
// icons
|
||||
import { ChevronDownIcon, ListBulletIcon } from "@heroicons/react/24/outline";
|
||||
import { Squares2X2Icon } from "@heroicons/react/20/solid";
|
||||
import { getStateGroupIcon } from "components/icons";
|
||||
import { getPriorityIcon } from "components/icons/priority-icon";
|
||||
// helpers
|
||||
import { replaceUnderscoreIfSnakeCase } from "helpers/string.helper";
|
||||
import { getStatesList } from "helpers/state.helper";
|
||||
// types
|
||||
import { IIssue, IIssueLabels, Properties } from "types";
|
||||
import { IIssueLabels, Properties } from "types";
|
||||
// fetch-keys
|
||||
import { PROJECT_ISSUE_LABELS, PROJECT_MEMBERS, STATE_LIST } from "constants/fetch-keys";
|
||||
// constants
|
||||
import { GROUP_BY_OPTIONS, ORDER_BY_OPTIONS, FILTER_ISSUE_OPTIONS } from "constants/issue";
|
||||
import { PRIORITIES } from "constants/project";
|
||||
|
||||
export const IssuesFilterView: React.FC = () => {
|
||||
const router = useRouter();
|
||||
@ -97,7 +100,7 @@ export const IssuesFilterView: React.FC = () => {
|
||||
</button>
|
||||
</div>
|
||||
<MultiLevelDropdown
|
||||
label="Filter"
|
||||
label="Filters"
|
||||
onSelect={(option) => {
|
||||
setFilters({
|
||||
...filters,
|
||||
@ -116,7 +119,11 @@ export const IssuesFilterView: React.FC = () => {
|
||||
children: [
|
||||
...PRIORITIES.map((priority) => ({
|
||||
id: priority ?? "none",
|
||||
label: priority ?? "None",
|
||||
label: (
|
||||
<div className="flex items-center gap-2">
|
||||
{getPriorityIcon(priority)} {priority ?? "None"}
|
||||
</div>
|
||||
),
|
||||
value: {
|
||||
key: "priority",
|
||||
value: priority,
|
||||
@ -132,7 +139,11 @@ export const IssuesFilterView: React.FC = () => {
|
||||
children: [
|
||||
...statesList.map((state) => ({
|
||||
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: {
|
||||
key: "state",
|
||||
value: state.id,
|
||||
@ -148,7 +159,14 @@ export const IssuesFilterView: React.FC = () => {
|
||||
children: [
|
||||
...(members?.map((member) => ({
|
||||
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: {
|
||||
key: "assignee",
|
||||
value: member.member.id,
|
||||
@ -163,12 +181,12 @@ export const IssuesFilterView: React.FC = () => {
|
||||
{({ open }) => (
|
||||
<>
|
||||
<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"
|
||||
}`}
|
||||
>
|
||||
View
|
||||
<ChevronDownIcon className="h-4 w-4" aria-hidden="true" />
|
||||
<ChevronDownIcon className="h-3 w-3" aria-hidden="true" />
|
||||
</Popover.Button>
|
||||
|
||||
<Transition
|
||||
|
@ -42,6 +42,7 @@ import {
|
||||
STATE_LIST,
|
||||
} from "constants/fetch-keys";
|
||||
import { EmptySpace, EmptySpaceItem } from "components/ui";
|
||||
import { PrimaryButton } from "components/ui/button/primary-button";
|
||||
|
||||
type Props = {
|
||||
type?: "issue" | "cycle" | "module";
|
||||
@ -496,20 +497,21 @@ export const IssuesView: React.FC<Props> = ({ type = "issue", openIssuesListModa
|
||||
})}
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<button
|
||||
{Object.keys(filters).length > 0 && (
|
||||
<PrimaryButton
|
||||
type="button"
|
||||
onClick={() =>
|
||||
setCreateViewModal({
|
||||
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" />
|
||||
<span>Save view</span>
|
||||
</button>
|
||||
</div>
|
||||
<PlusIcon className="h-4 w-4" />
|
||||
Save view
|
||||
</PrimaryButton>
|
||||
)}
|
||||
</div>
|
||||
<DragDropContext onDragEnd={handleOnDragEnd}>
|
||||
<StrictModeDroppable droppableId="trashBox">
|
||||
|
@ -18,7 +18,7 @@ export const IssuePrioritySelect: React.FC<Props> = ({ value, onChange }) => (
|
||||
label={
|
||||
<div className="flex items-center justify-center gap-2 text-xs">
|
||||
<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 className={`${value ? "text-gray-600" : "text-gray-500"} capitalize`}>
|
||||
{value ?? "Priority"}
|
||||
|
@ -136,8 +136,12 @@ export const SingleSidebarProject: React.FC<Props> = ({
|
||||
className={`${sidebarCollapse ? "" : "ml-[2.25rem]"} flex flex-col gap-y-1`}
|
||||
>
|
||||
{navigation(workspaceSlug as string, project?.id).map((item) => {
|
||||
if (item.name === "Cycles" && !project.cycle_view) return;
|
||||
if (item.name === "Modules" && !project.module_view) return;
|
||||
if (
|
||||
(item.name === "Cycles" && !project.cycle_view) ||
|
||||
(item.name === "Modules" && !project.module_view) ||
|
||||
(item.name === "Views" && !project.issue_views_view)
|
||||
)
|
||||
return;
|
||||
|
||||
return (
|
||||
<Link key={item.name} href={item.href}>
|
||||
|
@ -1,6 +1,7 @@
|
||||
import { Menu, Transition } from "@headlessui/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 = {
|
||||
label: string;
|
||||
@ -11,7 +12,7 @@ type MultiLevelDropdownProps = {
|
||||
selected?: boolean;
|
||||
children?: {
|
||||
id: string;
|
||||
label: string;
|
||||
label: string | JSX.Element;
|
||||
value: any;
|
||||
selected?: boolean;
|
||||
}[];
|
||||
@ -20,26 +21,27 @@ type MultiLevelDropdownProps = {
|
||||
direction?: "left" | "right";
|
||||
};
|
||||
|
||||
export const MultiLevelDropdown: React.FC<MultiLevelDropdownProps> = (props) => {
|
||||
const { label, options, onSelect, direction = "right" } = props;
|
||||
|
||||
export const MultiLevelDropdown: React.FC<MultiLevelDropdownProps> = ({
|
||||
label,
|
||||
options,
|
||||
onSelect,
|
||||
direction = "right",
|
||||
}) => {
|
||||
const [openChildFor, setOpenChildFor] = useState<string | null>(null);
|
||||
|
||||
return (
|
||||
<Menu as="div" className="relative inline-block text-left">
|
||||
<Menu as="div" className="relative z-10 inline-block text-left">
|
||||
{({ open }) => (
|
||||
<>
|
||||
<div>
|
||||
<Menu.Button
|
||||
onClick={() => {
|
||||
setOpenChildFor(null);
|
||||
}}
|
||||
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 ${
|
||||
onClick={() => 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 ${
|
||||
open ? "bg-gray-100 text-gray-900" : "text-gray-500"
|
||||
}`}
|
||||
>
|
||||
{label}
|
||||
<ChevronDownIcon className="h-4 w-4" aria-hidden="true" />
|
||||
<ChevronDownIcon className="h-3 w-3" aria-hidden="true" />
|
||||
</Menu.Button>
|
||||
</div>
|
||||
<Transition
|
||||
@ -53,10 +55,10 @@ export const MultiLevelDropdown: React.FC<MultiLevelDropdownProps> = (props) =>
|
||||
>
|
||||
<Menu.Items
|
||||
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) => (
|
||||
<div className="relative px-1 py-1" key={option.id}>
|
||||
<div className="relative p-1" key={option.id}>
|
||||
<Menu.Item
|
||||
as="button"
|
||||
onClick={(e: any) => {
|
||||
@ -81,7 +83,9 @@ export const MultiLevelDropdown: React.FC<MultiLevelDropdownProps> = (props) =>
|
||||
<div
|
||||
className={`${
|
||||
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 && (
|
||||
<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 && (
|
||||
<Menu.Items
|
||||
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"
|
||||
? "right-full -translate-x-2"
|
||||
: "left-full translate-x-2"
|
||||
? "right-full -translate-x-1"
|
||||
: "left-full translate-x-1"
|
||||
}`}
|
||||
>
|
||||
{option.children.map((child) => (
|
||||
<div className="relative px-1 py-1" key={child.id}>
|
||||
<Menu.Item as="div" className="flex items-center justify-between">
|
||||
{({ active }) => (
|
||||
<>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => {
|
||||
onSelect(child.value);
|
||||
}}
|
||||
className={`${
|
||||
active || option.selected ? "bg-gray-100" : "text-gray-900"
|
||||
} group flex w-full items-center rounded-md px-2 py-2 text-sm capitalize`}
|
||||
>
|
||||
{child.label}
|
||||
</button>
|
||||
</>
|
||||
)}
|
||||
<div className="p-1">
|
||||
{option.children.map((child) => (
|
||||
<Menu.Item
|
||||
key={child.id}
|
||||
as="button"
|
||||
type="button"
|
||||
onClick={() => {
|
||||
onSelect(child.value);
|
||||
}}
|
||||
className={({ active }) =>
|
||||
`${
|
||||
active || option.selected ? "bg-gray-100" : "text-gray-900"
|
||||
} flex w-full items-center rounded px-1 py-1.5 capitalize`
|
||||
}
|
||||
>
|
||||
{child.label}
|
||||
</Menu.Item>
|
||||
</div>
|
||||
))}
|
||||
))}
|
||||
</div>
|
||||
</Menu.Items>
|
||||
)}
|
||||
</div>
|
||||
|
@ -20,7 +20,7 @@ import { IProject, UserAuth } from "types";
|
||||
import type { NextPage, GetServerSidePropsContext } from "next";
|
||||
// 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 router = useRouter();
|
||||
@ -93,7 +93,7 @@ const FeaturesSettings: NextPage<UserAuth> = (props) => {
|
||||
<section className="space-y-8">
|
||||
<h3 className="text-2xl font-semibold">Features</h3>
|
||||
<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">
|
||||
<ContrastIcon color="#3f76ff" width={28} height={28} className="flex-shrink-0" />
|
||||
<div>
|
||||
@ -122,7 +122,7 @@ const FeaturesSettings: NextPage<UserAuth> = (props) => {
|
||||
/>
|
||||
</button>
|
||||
</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">
|
||||
<PeopleGroupIcon color="#ff6b00" width={28} height={28} className="flex-shrink-0" />
|
||||
<div>
|
||||
@ -151,6 +151,35 @@ const FeaturesSettings: NextPage<UserAuth> = (props) => {
|
||||
/>
|
||||
</button>
|
||||
</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 className="flex items-center gap-2">
|
||||
<a href="https://plane.so/" target="_blank" rel="noreferrer">
|
||||
|
1
apps/app/types/projects.d.ts
vendored
1
apps/app/types/projects.d.ts
vendored
@ -11,6 +11,7 @@ export interface IProject {
|
||||
id: string;
|
||||
identifier: string;
|
||||
is_favorite: boolean;
|
||||
issue_views_view: boolean;
|
||||
module_view: boolean;
|
||||
name: string;
|
||||
network: number;
|
||||
|
Loading…
Reference in New Issue
Block a user