Style/workspace project settings layout (#3520)

* style: project settings layout for mobile screens

* style: workspace settings layout for mobile screens
This commit is contained in:
Ramesh Kumar Chandra 2024-01-31 15:42:20 +05:30 committed by GitHub
parent 0d036e6bf5
commit 3a4c893368
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
9 changed files with 126 additions and 71 deletions

View File

@ -31,7 +31,6 @@ export const GoogleSignInButton: FC<Props> = (props) => {
size: "large", size: "large",
logo_alignment: "center", logo_alignment: "center",
text: type === "sign_in" ? "signin_with" : "signup_with", text: type === "sign_in" ? "signin_with" : "signup_with",
width: 384,
} as GsiButtonConfiguration // customization attributes } as GsiButtonConfiguration // customization attributes
); );
} catch (err) { } catch (err) {

View File

@ -77,7 +77,7 @@ export const WidgetIssuesList: React.FC<WidgetIssuesListProps> = (props) => {
})} })}
> >
Issues Issues
<span className="flex-shrink-0 bg-custom-primary-100/20 text-custom-primary-100 text-xs font-medium py-1 px-1.5 rounded-xl h-4 min-w-6 flex items-center text-center justify-center"> <span className="flex-shrink-0 bg-custom-primary-100/20 text-custom-primary-100 text-xs font-medium rounded-xl h-4 min-w-6 flex items-center text-center justify-center">
{totalIssues} {totalIssues}
</span> </span>
</h6> </h6>

View File

@ -72,14 +72,14 @@ export const IssuesByStateGroupWidget: React.FC<WidgetProps> = observer((props)
startedCount > 0 startedCount > 0
? "started" ? "started"
: unStartedCount > 0 : unStartedCount > 0
? "unstarted" ? "unstarted"
: backlogCount > 0 : backlogCount > 0
? "backlog" ? "backlog"
: completedCount > 0 : completedCount > 0
? "completed" ? "completed"
: canceledCount > 0 : canceledCount > 0
? "cancelled" ? "cancelled"
: null; : null;
setActiveStateGroup(stateGroup); setActiveStateGroup(stateGroup);
setDefaultStateGroup(stateGroup); setDefaultStateGroup(stateGroup);
@ -151,13 +151,13 @@ export const IssuesByStateGroupWidget: React.FC<WidgetProps> = observer((props)
/> />
</div> </div>
{totalCount > 0 ? ( {totalCount > 0 ? (
<div className="flex items-center pl-20 md:pl-11 lg:pl-14 pr-11 mt-11"> <div className="flex items-center pl-10 md:pl-11 lg:pl-14 pr-11 mt-11">
<div className="flex md:flex-col lg:flex-row items-center gap-x-10 gap-y-8 w-full"> <div className="flex flex-col sm:flex-row md:flex-row lg:flex-row items-center justify-evenly gap-x-10 gap-y-8 w-full">
<div className="w-full flex justify-center"> <div>
<PieGraph <PieGraph
data={chartData} data={chartData}
height="220px" height="220px"
width="220px" width="200px"
innerRadius={0.6} innerRadius={0.6}
cornerRadius={5} cornerRadius={5}
colors={(datum) => datum.data.color} colors={(datum) => datum.data.color}
@ -189,7 +189,7 @@ export const IssuesByStateGroupWidget: React.FC<WidgetProps> = observer((props)
layers={["arcs", CenteredMetric]} layers={["arcs", CenteredMetric]}
/> />
</div> </div>
<div className="justify-self-end space-y-6 w-min whitespace-nowrap"> <div className="space-y-6 w-min whitespace-nowrap">
{chartData.map((item) => ( {chartData.map((item) => (
<div key={item.id} className="flex items-center justify-between gap-6"> <div key={item.id} className="flex items-center justify-between gap-6">
<div className="flex items-center gap-2.5 w-24"> <div className="flex items-center gap-2.5 w-24">

View File

@ -2,13 +2,13 @@ import { FC } from "react";
import { useRouter } from "next/router"; import { useRouter } from "next/router";
import { observer } from "mobx-react-lite"; import { observer } from "mobx-react-lite";
// ui // ui
import { Breadcrumbs } from "@plane/ui"; import { Breadcrumbs, CustomMenu } from "@plane/ui";
// helper // helper
import { renderEmoji } from "helpers/emoji.helper"; import { renderEmoji } from "helpers/emoji.helper";
// hooks // hooks
import { useProject, useUser } from "hooks/store"; import { useProject, useUser } from "hooks/store";
// constants // constants
import { EUserProjectRoles } from "constants/project"; import { EUserProjectRoles, PROJECT_SETTINGS_LINKS } from "constants/project";
// components // components
import { SidebarHamburgerToggle } from "components/core/sidebar/sidebar-menu-hamburger-toggle"; import { SidebarHamburgerToggle } from "components/core/sidebar/sidebar-menu-hamburger-toggle";
@ -20,7 +20,7 @@ export const ProjectSettingHeader: FC<IProjectSettingHeader> = observer((props)
const { title } = props; const { title } = props;
// router // router
const router = useRouter(); const router = useRouter();
const { workspaceSlug } = router.query; const { workspaceSlug, projectId } = router.query;
// store hooks // store hooks
const { const {
membership: { currentProjectRole }, membership: { currentProjectRole },
@ -31,29 +31,48 @@ export const ProjectSettingHeader: FC<IProjectSettingHeader> = observer((props)
return ( return (
<div className="relative z-10 flex h-[3.75rem] w-full flex-shrink-0 flex-row items-center justify-between gap-x-2 gap-y-4 border-b border-custom-border-200 bg-custom-sidebar-background-100 p-4"> <div className="relative z-10 flex h-[3.75rem] w-full flex-shrink-0 flex-row items-center justify-between gap-x-2 gap-y-4 border-b border-custom-border-200 bg-custom-sidebar-background-100 p-4">
<SidebarHamburgerToggle />
<div className="flex w-full flex-grow items-center gap-2 overflow-ellipsis whitespace-nowrap"> <div className="flex w-full flex-grow items-center gap-2 overflow-ellipsis whitespace-nowrap">
<SidebarHamburgerToggle/>
<div> <div>
<Breadcrumbs> <div className="z-50">
<Breadcrumbs.BreadcrumbItem <Breadcrumbs>
type="text" <Breadcrumbs.BreadcrumbItem
label={currentProjectDetails?.name ?? "Project"} type="text"
icon={ label={currentProjectDetails?.name ?? "Project"}
currentProjectDetails?.emoji ? ( icon={
renderEmoji(currentProjectDetails.emoji) currentProjectDetails?.emoji ? (
) : currentProjectDetails?.icon_prop ? ( renderEmoji(currentProjectDetails.emoji)
renderEmoji(currentProjectDetails.icon_prop) ) : currentProjectDetails?.icon_prop ? (
) : ( renderEmoji(currentProjectDetails.icon_prop)
<span className="grid h-7 w-7 flex-shrink-0 place-items-center rounded bg-gray-700 uppercase text-white"> ) : (
{currentProjectDetails?.name.charAt(0)} <span className="grid h-7 w-7 flex-shrink-0 place-items-center rounded bg-gray-700 uppercase text-white">
</span> {currentProjectDetails?.name.charAt(0)}
) </span>
} )
link={`/${workspaceSlug}/projects/${currentProjectDetails?.id}/issues`} }
/> link={`/${workspaceSlug}/projects/${currentProjectDetails?.id}/issues`}
<Breadcrumbs.BreadcrumbItem type="text" label={title} /> />
</Breadcrumbs> <div className="hidden sm:hidden md:block lg:block">
<Breadcrumbs.BreadcrumbItem type="text" label={title} />
</div>
</Breadcrumbs>
</div>
</div> </div>
<CustomMenu
className="flex-shrink-0 block sm:block md:hidden lg:hidden"
maxHeight="lg"
customButton={
<span className="text-xs px-1.5 py-1 border rounded-md text-custom-text-200 border-custom-border-300">{title}</span>
}
placement="bottom-start"
closeOnSelect
>
{PROJECT_SETTINGS_LINKS.map((item) => (
<CustomMenu.MenuItem key={item.key} onClick={() => router.push(`/${workspaceSlug}/projects/${projectId}${item.href}`)}>
{item.label}
</CustomMenu.MenuItem>
))}
</CustomMenu>
</div> </div>
</div> </div>
); );

View File

@ -1,12 +1,13 @@
import { FC } from "react"; import { FC } from "react";
import { useRouter } from "next/router"; import { useRouter } from "next/router";
// ui // ui
import { Breadcrumbs } from "@plane/ui"; import { Breadcrumbs, CustomMenu } from "@plane/ui";
import { Settings } from "lucide-react"; import { Settings } from "lucide-react";
// hooks // hooks
import { observer } from "mobx-react-lite"; import { observer } from "mobx-react-lite";
// components // components
import { SidebarHamburgerToggle } from "components/core/sidebar/sidebar-menu-hamburger-toggle"; import { SidebarHamburgerToggle } from "components/core/sidebar/sidebar-menu-hamburger-toggle";
import { WORKSPACE_SETTINGS_LINKS } from "constants/workspace";
export interface IWorkspaceSettingHeader { export interface IWorkspaceSettingHeader {
title: string; title: string;
@ -21,7 +22,7 @@ export const WorkspaceSettingHeader: FC<IWorkspaceSettingHeader> = observer((pro
return ( return (
<div className="relative z-10 flex h-[3.75rem] w-full flex-shrink-0 flex-row items-center justify-between gap-x-2 gap-y-4 border-b border-custom-border-200 bg-custom-sidebar-background-100 p-4"> <div className="relative z-10 flex h-[3.75rem] w-full flex-shrink-0 flex-row items-center justify-between gap-x-2 gap-y-4 border-b border-custom-border-200 bg-custom-sidebar-background-100 p-4">
<div className="flex w-full flex-grow items-center gap-2 overflow-ellipsis whitespace-nowrap"> <div className="flex w-full flex-grow items-center gap-2 overflow-ellipsis whitespace-nowrap">
<SidebarHamburgerToggle/> <SidebarHamburgerToggle />
<div> <div>
<Breadcrumbs> <Breadcrumbs>
<Breadcrumbs.BreadcrumbItem <Breadcrumbs.BreadcrumbItem
@ -30,9 +31,26 @@ export const WorkspaceSettingHeader: FC<IWorkspaceSettingHeader> = observer((pro
icon={<Settings className="h-4 w-4 text-custom-text-300" />} icon={<Settings className="h-4 w-4 text-custom-text-300" />}
link={`/${workspaceSlug}/settings`} link={`/${workspaceSlug}/settings`}
/> />
<Breadcrumbs.BreadcrumbItem type="text" label={title} /> <div className="hidden sm:hidden md:block lg:block">
<Breadcrumbs.BreadcrumbItem type="text" label={title} />
</div>
</Breadcrumbs> </Breadcrumbs>
</div> </div>
<CustomMenu
className="flex-shrink-0 block sm:block md:hidden lg:hidden"
maxHeight="lg"
customButton={
<span className="text-xs px-1.5 py-1 border rounded-md text-custom-text-200 border-custom-border-300">{title}</span>
}
placement="bottom-start"
closeOnSelect
>
{WORKSPACE_SETTINGS_LINKS.map((item) => (
<CustomMenu.MenuItem key={item.key} onClick={() => router.push(`/${workspaceSlug}${item.href}`)}>
{item.label}
</CustomMenu.MenuItem>
))}
</CustomMenu>
</div> </div>
</div> </div>
); );

View File

@ -147,10 +147,10 @@ export const ProjectDetailsForm: FC<IProjectDetailsForm> = (props) => {
return ( return (
<form onSubmit={handleSubmit(onSubmit)}> <form onSubmit={handleSubmit(onSubmit)}>
<div className="relative mt-6 h-44 w-full"> <div className="relative mt-6 h-44 w-full">
<div className="absolute inset-0 z-[1] bg-gradient-to-t from-black/50 to-transparent" /> <div className="absolute inset-0 bg-gradient-to-t from-black/50 to-transparent" />
<img src={watch("cover_image")!} alt={watch("cover_image")!} className="h-44 w-full rounded-md object-cover" /> <img src={watch("cover_image")!} alt={watch("cover_image")!} className="h-44 w-full rounded-md object-cover" />
<div className="absolute bottom-4 z-10 flex w-full items-end justify-between gap-3 px-4"> <div className="absolute bottom-4 z-5 flex w-full items-end justify-between gap-3 px-4">
<div className="flex flex-grow gap-3 truncate"> <div className="flex flex-grow gap-3 truncate">
<div className="flex h-[52px] w-[52px] flex-shrink-0 items-center justify-center rounded-lg bg-custom-background-90"> <div className="flex h-[52px] w-[52px] flex-shrink-0 items-center justify-center rounded-lg bg-custom-background-90">
<div className="grid h-7 w-7 place-items-center"> <div className="grid h-7 w-7 place-items-center">

View File

@ -1,4 +1,4 @@
import { Fragment } from "react"; import { Fragment, useState } from "react";
import { useRouter } from "next/router"; import { useRouter } from "next/router";
import { observer } from "mobx-react-lite"; import { observer } from "mobx-react-lite";
import Link from "next/link"; import Link from "next/link";
@ -6,6 +6,7 @@ import { useTheme } from "next-themes";
import { Menu, Transition } from "@headlessui/react"; import { Menu, Transition } from "@headlessui/react";
import { mutate } from "swr"; import { mutate } from "swr";
import { Check, ChevronDown, CircleUserRound, LogOut, Mails, PlusSquare, Settings, UserCircle2 } from "lucide-react"; import { Check, ChevronDown, CircleUserRound, LogOut, Mails, PlusSquare, Settings, UserCircle2 } from "lucide-react";
import { usePopper } from "react-popper";
// hooks // hooks
import { useApplication, useUser, useWorkspace } from "hooks/store"; import { useApplication, useUser, useWorkspace } from "hooks/store";
// hooks // hooks
@ -14,7 +15,6 @@ import useToast from "hooks/use-toast";
import { Avatar, Loader } from "@plane/ui"; import { Avatar, Loader } from "@plane/ui";
// types // types
import { IWorkspace } from "@plane/types"; import { IWorkspace } from "@plane/types";
// Static Data // Static Data
const userLinks = (workspaceSlug: string, userId: string) => [ const userLinks = (workspaceSlug: string, userId: string) => [
{ {
@ -36,7 +36,6 @@ const userLinks = (workspaceSlug: string, userId: string) => [
icon: Settings, icon: Settings,
}, },
]; ];
const profileLinks = (workspaceSlug: string, userId: string) => [ const profileLinks = (workspaceSlug: string, userId: string) => [
{ {
name: "View profile", name: "View profile",
@ -49,7 +48,6 @@ const profileLinks = (workspaceSlug: string, userId: string) => [
link: "/profile", link: "/profile",
}, },
]; ];
export const WorkspaceSidebarDropdown = observer(() => { export const WorkspaceSidebarDropdown = observer(() => {
// router // router
const router = useRouter(); const router = useRouter();
@ -64,12 +62,25 @@ export const WorkspaceSidebarDropdown = observer(() => {
// hooks // hooks
const { setToastAlert } = useToast(); const { setToastAlert } = useToast();
const { setTheme } = useTheme(); const { setTheme } = useTheme();
// popper-js refs
const [referenceElement, setReferenceElement] = useState<HTMLButtonElement | null>(null);
const [popperElement, setPopperElement] = useState<HTMLDivElement | null>(null);
// popper-js init
const { styles, attributes } = usePopper(referenceElement, popperElement, {
placement: "right",
modifiers: [
{
name: "preventOverflow",
options: {
padding: 12,
},
},
],
});
const handleWorkspaceNavigation = (workspace: IWorkspace) => const handleWorkspaceNavigation = (workspace: IWorkspace) =>
updateCurrentUser({ updateCurrentUser({
last_workspace_id: workspace?.id, last_workspace_id: workspace?.id,
}); });
const handleSignOut = async () => { const handleSignOut = async () => {
await signOut() await signOut()
.then(() => { .then(() => {
@ -85,16 +96,12 @@ export const WorkspaceSidebarDropdown = observer(() => {
}) })
); );
}; };
const handleItemClick = () => { const handleItemClick = () => {
console.log('CLICKED')
if (window.innerWidth < 768) { if (window.innerWidth < 768) {
toggleSidebar(); toggleSidebar();
} }
}; };
const workspacesList = Object.values(workspaces ?? {}); const workspacesList = Object.values(workspaces ?? {});
// TODO: fix workspaces list scroll // TODO: fix workspaces list scroll
return ( return (
<div className="flex items-center gap-x-3 gap-y-2 px-4 pt-4"> <div className="flex items-center gap-x-3 gap-y-2 px-4 pt-4">
@ -121,14 +128,12 @@ export const WorkspaceSidebarDropdown = observer(() => {
activeWorkspace?.name?.charAt(0) ?? "..." activeWorkspace?.name?.charAt(0) ?? "..."
)} )}
</div> </div>
{!sidebarCollapsed && ( {!sidebarCollapsed && (
<h4 className="truncate text-base font-medium text-custom-text-100"> <h4 className="truncate text-base font-medium text-custom-text-100">
{activeWorkspace?.name ? activeWorkspace.name : "Loading..."} {activeWorkspace?.name ? activeWorkspace.name : "Loading..."}
</h4> </h4>
)} )}
</div> </div>
{!sidebarCollapsed && ( {!sidebarCollapsed && (
<ChevronDown <ChevronDown
className={`mx-1 hidden h-4 w-4 flex-shrink-0 group-hover/menu-button:block ${open ? "rotate-180" : "" className={`mx-1 hidden h-4 w-4 flex-shrink-0 group-hover/menu-button:block ${open ? "rotate-180" : ""
@ -137,7 +142,6 @@ export const WorkspaceSidebarDropdown = observer(() => {
)} )}
</div> </div>
</Menu.Button> </Menu.Button>
<Transition <Transition
as={Fragment} as={Fragment}
enter="transition ease-out duration-100" enter="transition ease-out duration-100"
@ -185,7 +189,6 @@ export const WorkspaceSidebarDropdown = observer(() => {
workspace?.name?.charAt(0) ?? "..." workspace?.name?.charAt(0) ?? "..."
)} )}
</span> </span>
<h5 <h5
className={`truncate text-sm font-medium ${workspaceSlug === workspace.slug ? "" : "text-custom-text-200" className={`truncate text-sm font-medium ${workspaceSlug === workspace.slug ? "" : "text-custom-text-200"
}`} }`}
@ -226,9 +229,14 @@ export const WorkspaceSidebarDropdown = observer(() => {
</Menu.Item> </Menu.Item>
</Link> </Link>
{userLinks(workspaceSlug?.toString() ?? "", currentUser?.id ?? "").map((link, index) => ( {userLinks(workspaceSlug?.toString() ?? "", currentUser?.id ?? "").map((link, index) => (
<Link key={link.key} href={link.href} className="w-full" onClick={() => { <Link
if (index > 0) handleItemClick(); key={link.key}
}}> href={link.href}
className="w-full"
onClick={() => {
if (index > 0) handleItemClick();
}}
>
<Menu.Item <Menu.Item
as="div" as="div"
className="flex items-center gap-2 rounded px-2 py-1 text-sm text-custom-sidebar-text-200 hover:bg-custom-sidebar-background-80 font-medium" className="flex items-center gap-2 rounded px-2 py-1 text-sm text-custom-sidebar-text-200 hover:bg-custom-sidebar-background-80 font-medium"
@ -256,10 +264,9 @@ export const WorkspaceSidebarDropdown = observer(() => {
</> </>
)} )}
</Menu> </Menu>
{!sidebarCollapsed && ( {!sidebarCollapsed && (
<Menu as="div" className="relative flex-shrink-0"> <Menu as="div" className="relative flex-shrink-0">
<Menu.Button className="grid place-items-center outline-none"> <Menu.Button className="grid place-items-center outline-none" ref={setReferenceElement}>
<Avatar <Avatar
name={currentUser?.display_name} name={currentUser?.display_name}
src={currentUser?.avatar} src={currentUser?.avatar}
@ -268,7 +275,6 @@ export const WorkspaceSidebarDropdown = observer(() => {
className="!text-base" className="!text-base"
/> />
</Menu.Button> </Menu.Button>
<Transition <Transition
as={Fragment} as={Fragment}
enter="transition ease-out duration-100" enter="transition ease-out duration-100"
@ -281,11 +287,20 @@ export const WorkspaceSidebarDropdown = observer(() => {
<Menu.Items <Menu.Items
className="absolute left-0 z-20 mt-1 flex w-52 origin-top-left flex-col divide-y className="absolute left-0 z-20 mt-1 flex w-52 origin-top-left flex-col divide-y
divide-custom-sidebar-border-200 rounded-md border border-custom-sidebar-border-200 bg-custom-sidebar-background-100 px-1 py-2 text-xs shadow-lg outline-none" divide-custom-sidebar-border-200 rounded-md border border-custom-sidebar-border-200 bg-custom-sidebar-background-100 px-1 py-2 text-xs shadow-lg outline-none"
ref={setPopperElement}
style={styles.popper}
{...attributes.popper}
> >
<div className="flex flex-col gap-2.5 pb-2"> <div className="flex flex-col gap-2.5 pb-2">
<span className="px-2 text-custom-sidebar-text-200">{currentUser?.email}</span> <span className="px-2 text-custom-sidebar-text-200">{currentUser?.email}</span>
{profileLinks(workspaceSlug?.toString() ?? "", currentUser?.id ?? "").map((link, index) => ( {profileLinks(workspaceSlug?.toString() ?? "", currentUser?.id ?? "").map((link, index) => (
<Link key={index} href={link.link} onClick={() => { if (index == 0) handleItemClick(); }}> <Link
key={index}
href={link.link}
onClick={() => {
if (index == 0) handleItemClick();
}}
>
<Menu.Item key={index} as="div"> <Menu.Item key={index} as="div">
<span className="flex w-full items-center gap-2 rounded px-2 py-1 hover:bg-custom-sidebar-background-80"> <span className="flex w-full items-center gap-2 rounded px-2 py-1 hover:bg-custom-sidebar-background-80">
<link.icon className="h-4 w-4 stroke-[1.5]" /> <link.icon className="h-4 w-4 stroke-[1.5]" />
@ -323,4 +338,4 @@ export const WorkspaceSidebarDropdown = observer(() => {
)} )}
</div> </div>
); );
}); });

View File

@ -41,11 +41,13 @@ export const ProjectSettingLayout: FC<IProjectSettingLayout> = observer((props)
} }
/> />
) : ( ) : (
<div className="flex h-full w-full gap-2 overflow-x-hidden overflow-y-scroll"> <div className="inset-y-0 z-20 flex flex-grow-0 h-full w-full gap-2 overflow-x-hidden overflow-y-scroll">
<div className="w-80 flex-shrink-0 overflow-y-hidden pt-8"> <div className="w-80 flex-shrink-0 overflow-y-hidden pt-8 sm:hidden hidden md:block lg:block">
<ProjectSettingsSidebar /> <ProjectSettingsSidebar />
</div> </div>
{children} <div className="w-full pl-10 sm:pl-10 md:pl-0 lg:pl-0">
{children}
</div>
</div> </div>
); );
}); });

View File

@ -10,11 +10,13 @@ export const WorkspaceSettingLayout: FC<IWorkspaceSettingLayout> = (props) => {
const { children } = props; const { children } = props;
return ( return (
<div className="flex h-full w-full gap-2 overflow-x-hidden overflow-y-scroll"> <div className="inset-y-0 z-20 flex h-full w-full gap-2 overflow-x-hidden overflow-y-scroll">
<div className="w-80 flex-shrink-0 overflow-y-hidden pt-8"> <div className="w-80 flex-shrink-0 overflow-y-hidden pt-8 sm:hidden hidden md:block lg:block">
<WorkspaceSettingsSidebar /> <WorkspaceSettingsSidebar />
</div> </div>
{children} <div className="w-full pl-10 sm:pl-10 md:pl-0 lg:pl-0">
{children}
</div>
</div> </div>
); );
}; };