Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[WEB-1120] chore: workspace view quick action enhancement #4324

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -161,6 +161,7 @@ export const AllIssueQuickActions: React.FC<IQuickActionProps> = observer((props
portalElement={portalElement}
placement={placements}
menuItemsClassName="z-[14]"
maxHeight="lg"
closeOnSelect
>
{MENU_ITEMS.map((item) => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,7 @@ export const ArchivedIssueQuickActions: React.FC<IQuickActionProps> = observer((
portalElement={portalElement}
placement={placements}
menuItemsClassName="z-[14]"
maxHeight="lg"
closeOnSelect
>
{MENU_ITEMS.map((item) => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -181,6 +181,7 @@ export const CycleIssueQuickActions: React.FC<IQuickActionProps> = observer((pro
customButton={customActionButton}
portalElement={portalElement}
menuItemsClassName="z-[14]"
maxHeight="lg"
closeOnSelect
>
{MENU_ITEMS.map((item) => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,7 @@ export const DraftIssueQuickActions: React.FC<IQuickActionProps> = observer((pro
portalElement={portalElement}
placement={placements}
menuItemsClassName="z-[14]"
maxHeight="lg"
closeOnSelect
>
{MENU_ITEMS.map((item) => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -178,6 +178,7 @@ export const ModuleIssueQuickActions: React.FC<IQuickActionProps> = observer((pr
customButton={customActionButton}
portalElement={portalElement}
menuItemsClassName="z-[14]"
maxHeight="lg"
closeOnSelect
>
{MENU_ITEMS.map((item) => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -171,6 +171,7 @@ export const ProjectIssueQuickActions: React.FC<IQuickActionProps> = observer((p
customButton={customActionButton}
portalElement={portalElement}
menuItemsClassName="z-[14]"
maxHeight="lg"
closeOnSelect
>
{MENU_ITEMS.map((item) => {
Expand Down
127 changes: 127 additions & 0 deletions web/components/workspace/views/default-view-quick-action.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
import { observer } from "mobx-react";
import Link from "next/link";
import { ExternalLink, LinkIcon } from "lucide-react";
// ui
import { TStaticViewTypes } from "@plane/types";
import { ContextMenu, CustomMenu, TContextMenuItem, TOAST_TYPE, setToast } from "@plane/ui";
// helpers
import { cn } from "@/helpers/common.helper";
import { copyUrlToClipboard } from "@/helpers/string.helper";

type Props = {
parentRef: React.RefObject<HTMLElement>;
workspaceSlug: string;
globalViewId: string | undefined;
view: {
key: TStaticViewTypes;
label: string;
};
};

export const DefaultWorkspaceViewQuickActions: React.FC<Props> = observer((props) => {
const { parentRef, globalViewId, view, workspaceSlug } = props;

const viewLink = `${workspaceSlug}/workspace-views/${view.key}`;
const handleCopyText = () =>
copyUrlToClipboard(viewLink).then(() => {
setToast({
type: TOAST_TYPE.SUCCESS,
title: "Link Copied!",
message: "View link copied to clipboard.",
});
});
const handleOpenInNewTab = () => window.open(`/${viewLink}`, "_blank");

const MENU_ITEMS: TContextMenuItem[] = [
{
key: "open-new-tab",
action: handleOpenInNewTab,
title: "Open in new tab",
icon: ExternalLink,
},
{
key: "copy-link",
action: handleCopyText,
title: "Copy link",
icon: LinkIcon,
},
];

return (
<>
<ContextMenu parentRef={parentRef} items={MENU_ITEMS} />

<CustomMenu
customButton={
<>
{view.key === globalViewId ? (
<span
className={`flex min-w-min flex-shrink-0 whitespace-nowrap border-b-2 p-3 text-sm font-medium outline-none ${
view.key === globalViewId
? "border-custom-primary-100 text-custom-primary-100"
: "border-transparent hover:border-custom-border-200 hover:text-custom-text-400"
}`}
>
{view.label}
</span>
) : (
<Link
key={view.key}
id={`global-view-${view.key}`}
href={`/${workspaceSlug}/workspace-views/${view.key}`}
>
<span
className={`flex min-w-min flex-shrink-0 whitespace-nowrap border-b-2 p-3 text-sm font-medium outline-none ${
view.key === globalViewId
? "border-custom-primary-100 text-custom-primary-100"
: "border-transparent hover:border-custom-border-200 hover:text-custom-text-400"
}`}
>
{view.label}
</span>
</Link>
)}
</>
}
placement="bottom-end"
menuItemsClassName="z-20"
closeOnSelect
>
{MENU_ITEMS.map((item) => {
if (item.shouldRender === false) return null;
return (
<CustomMenu.MenuItem
key={item.key}
onClick={(e) => {
e.preventDefault();
e.stopPropagation();
item.action();
}}
className={cn(
"flex items-center gap-2",
{
"text-custom-text-400": item.disabled,
},
item.className
)}
>
{item.icon && <item.icon className={cn("h-3 w-3", item.iconClassName)} />}
<div>
<h5>{item.title}</h5>
{item.description && (
<p
className={cn("text-custom-text-300 whitespace-pre-line", {
"text-custom-text-400": item.disabled,
})}
>
{item.description}
</p>
)}
</div>
</CustomMenu.MenuItem>
);
})}
</CustomMenu>
</>
);
});
79 changes: 49 additions & 30 deletions web/components/workspace/views/header.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,16 @@
import React, { useEffect, useRef, useState } from "react";
import { observer } from "mobx-react-lite";
import Link from "next/link";
import { useRouter } from "next/router";
// icons
import { Plus } from "lucide-react";
// types
import { TStaticViewTypes } from "@plane/types";
// components
import { CreateUpdateWorkspaceViewModal } from "@/components/workspace";
import {
CreateUpdateWorkspaceViewModal,
DefaultWorkspaceViewQuickActions,
WorkspaceViewQuickActions,
} from "@/components/workspace";
// constants
import { GLOBAL_VIEW_OPENED } from "@/constants/event-tracker";
import { DEFAULT_GLOBAL_VIEWS_LIST, EUserWorkspaceRoles } from "@/constants/workspace";
Expand All @@ -14,6 +19,8 @@ import { useEventTracker, useGlobalView, useUser } from "@/hooks/store";

const ViewTab = observer((props: { viewId: string }) => {
const { viewId } = props;
// refs
const parentRef = useRef<HTMLDivElement>(null);
// router
const router = useRouter();
const { workspaceSlug, globalViewId } = router.query;
Expand All @@ -22,30 +29,54 @@ const ViewTab = observer((props: { viewId: string }) => {

const view = getViewDetailsById(viewId);

if (!view) return null;
if (!view || !workspaceSlug || !globalViewId) return null;

return (
<Link key={viewId} id={`global-view-${viewId}`} href={`/${workspaceSlug}/workspace-views/${viewId}`}>
<span
className={`flex min-w-min flex-shrink-0 whitespace-nowrap border-b-2 p-3 text-sm font-medium outline-none ${
viewId === globalViewId
? "border-custom-primary-100 text-custom-primary-100"
: "border-transparent hover:border-custom-border-200 hover:text-custom-text-400"
}`}
>
{view.name}
</span>
</Link>
<div ref={parentRef} className="relative">
<WorkspaceViewQuickActions
parentRef={parentRef}
view={view}
viewId={viewId}
globalViewId={globalViewId?.toString()}
workspaceSlug={workspaceSlug?.toString()}
/>
</div>
);
});

const DefaultViewTab = (props: {
tab: {
key: TStaticViewTypes;
label: string;
};
}) => {
const { tab } = props;
// refs
const parentRef = useRef<HTMLDivElement>(null);
// router
const router = useRouter();
const { workspaceSlug, globalViewId } = router.query;

if (!workspaceSlug || !globalViewId) return null;
return (
<div key={tab.key} ref={parentRef} className="relative">
<DefaultWorkspaceViewQuickActions
parentRef={parentRef}
globalViewId={globalViewId?.toString()}
workspaceSlug={workspaceSlug?.toString()}
view={tab}
/>
</div>
);
};

export const GlobalViewsHeader: React.FC = observer(() => {
// states
const [createViewModal, setCreateViewModal] = useState(false);
const containerRef = useRef<HTMLDivElement>(null);
// router
const router = useRouter();
const { workspaceSlug, globalViewId } = router.query;
const { globalViewId } = router.query;
// store hooks
const { currentWorkspaceViews } = useGlobalView();
const {
Expand Down Expand Up @@ -82,23 +113,11 @@ export const GlobalViewsHeader: React.FC = observer(() => {
ref={containerRef}
className="flex w-full items-center overflow-x-auto px-4 horizontal-scrollbar scrollbar-sm"
>
{DEFAULT_GLOBAL_VIEWS_LIST.map((tab) => (
<Link key={tab.key} id={`global-view-${tab.key}`} href={`/${workspaceSlug}/workspace-views/${tab.key}`}>
<span
className={`flex min-w-min flex-shrink-0 whitespace-nowrap border-b-2 p-3 text-sm font-medium outline-none ${
tab.key === globalViewId
? "border-custom-primary-100 text-custom-primary-100"
: "border-transparent hover:border-custom-border-200 hover:text-custom-text-400"
}`}
>
{tab.label}
</span>
</Link>
{DEFAULT_GLOBAL_VIEWS_LIST.map((tab, index) => (
<DefaultViewTab key={`${tab.key}-${index}`} tab={tab} />
))}

{currentWorkspaceViews?.map((viewId) => (
<ViewTab key={viewId} viewId={viewId} />
))}
{currentWorkspaceViews?.map((viewId) => <ViewTab key={viewId} viewId={viewId} />)}
</div>

{isAuthorizedUser && (
Expand Down
2 changes: 2 additions & 0 deletions web/components/workspace/views/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,5 @@ export * from "./header";
export * from "./modal";
export * from "./view-list-item";
export * from "./views-list";
export * from "./quick-action";
export * from "./default-view-quick-action";
Loading
Loading