Skip to content

Commit e16e468

Browse files
sabith-tusriramveeraghanta
authored andcommitted
style: new empty state ui (#2923)
1 parent d2a3d00 commit e16e468

File tree

15 files changed

+252
-89
lines changed

15 files changed

+252
-89
lines changed
+113
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
import React, { useState } from "react";
2+
3+
import Image from "next/image";
4+
5+
// ui
6+
import { Button } from "@plane/ui";
7+
8+
type Props = {
9+
title: string;
10+
description?: React.ReactNode;
11+
image: any;
12+
comicBox?: {
13+
direction: "left" | "right";
14+
title: string;
15+
description: string;
16+
extraPadding?: boolean;
17+
};
18+
primaryButton?: {
19+
icon?: any;
20+
text: string;
21+
onClick: () => void;
22+
};
23+
secondaryButton?: React.ReactNode;
24+
disabled?: boolean;
25+
};
26+
27+
export const NewEmptyState: React.FC<Props> = ({
28+
title,
29+
description,
30+
image,
31+
primaryButton,
32+
secondaryButton,
33+
disabled = false,
34+
comicBox,
35+
}) => {
36+
const [isHovered, setIsHovered] = useState(false);
37+
38+
const handleMouseEnter = () => {
39+
setIsHovered(true);
40+
};
41+
42+
const handleMouseLeave = () => {
43+
setIsHovered(false);
44+
};
45+
return (
46+
<div className=" flex flex-col justify-center items-center h-full w-full ">
47+
<div className="border border-custom-border-200 rounded-xl px-10 py-7 flex flex-col gap-5 max-w-6xl m-5 md:m-16 shadow-sm">
48+
<h3 className="font-semibold text-2xl">{title}</h3>
49+
{description && <p className=" text-lg">{description}</p>}
50+
<div className="relative w-full max-w-6xl">
51+
<Image src={image} className="w-52 sm:w-60" alt={primaryButton?.text} />
52+
</div>
53+
54+
<div className="flex justify-center items-start relative">
55+
{primaryButton && (
56+
<Button
57+
className={`max-w-min m-3 relative !px-6 ${comicBox?.direction === "left" ? "flex-row-reverse" : ""}`}
58+
size="lg"
59+
variant="primary"
60+
onClick={primaryButton.onClick}
61+
disabled={disabled}
62+
>
63+
{primaryButton.text}
64+
<div
65+
onMouseEnter={handleMouseEnter}
66+
onMouseLeave={handleMouseLeave}
67+
className={`bg-blue-300 absolute ${
68+
comicBox?.direction === "left" ? "left-0 ml-2" : "right-0 mr-2"
69+
} h-2.5 w-2.5 z-10 rounded-full animate-ping`}
70+
/>
71+
<div
72+
className={`bg-blue-400/40 absolute ${
73+
comicBox?.direction === "left" ? "left-0 ml-2.5" : "right-0 mr-2.5"
74+
} h-1.5 w-1.5 rounded-full`}
75+
/>
76+
</Button>
77+
)}
78+
{comicBox &&
79+
isHovered &&
80+
(comicBox.direction === "right" ? (
81+
<div
82+
className={`flex max-w-sm absolute top-0 left-1/2 ${
83+
comicBox?.extraPadding ? "ml-[125px]" : "ml-[90px]"
84+
} pb-5`}
85+
>
86+
<div className="relative w-0 h-0 border-t-[11px] mt-5 border-custom-border-200 border-b-[11px] border-r-[11px] border-y-transparent">
87+
<div className="absolute top-[-10px] right-[-12px] w-0 h-0 border-t-[10px] border-custom-background-100 border-b-[10px] border-r-[10px] border-y-transparent" />
88+
</div>
89+
<div className="border border-custom-border-200 rounded-md bg-custom-background-100">
90+
<h1 className="p-5">
91+
<h3 className="font-semibold text-lg">{comicBox?.title}</h3>
92+
<h4 className="text-sm mt-1">{comicBox?.description}</h4>
93+
</h1>
94+
</div>
95+
</div>
96+
) : (
97+
<div className="flex flex-row-reverse max-w-sm absolute top-0 right-1/2 mr-[90px] pb-5">
98+
<div className="relative w-0 h-0 border-t-[11px] mt-5 border-custom-border-200 border-b-[11px] border-l-[11px] border-y-transparent">
99+
<div className="absolute top-[-10px] left-[-12px] w-0 h-0 border-t-[10px] border-custom-background-100 border-b-[10px] border-l-[10px] border-y-transparent" />
100+
</div>
101+
<div className="border border-custom-border-200 rounded-md bg-custom-background-100">
102+
<h1 className="p-5">
103+
<h3 className="font-semibold text-lg">{comicBox?.title}</h3>
104+
<h4 className="text-sm mt-1">{comicBox?.description}</h4>
105+
</h1>
106+
</div>
107+
</div>
108+
))}
109+
</div>
110+
</div>
111+
</div>
112+
);
113+
};

web/components/issues/issue-layouts/empty-states/project.tsx

+12-5
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,9 @@ import { useMobxStore } from "lib/mobx/store-provider";
55
// components
66
import { EmptyState } from "components/common";
77
// assets
8-
import emptyIssue from "public/empty-state/issue.svg";
8+
import emptyIssue from "public/empty-state/empty_issues.webp";
99
import { EProjectStore } from "store/command-palette.store";
10+
import { NewEmptyState } from "components/common/new-empty-state";
1011

1112
export const ProjectEmptyState: React.FC = observer(() => {
1213
const {
@@ -16,12 +17,18 @@ export const ProjectEmptyState: React.FC = observer(() => {
1617

1718
return (
1819
<div className="h-full w-full grid place-items-center">
19-
<EmptyState
20-
title="Project issues will appear here"
21-
description="Issues help you track individual pieces of work. With Issues, keep track of what's going on, who is working on it, and what's done."
20+
<NewEmptyState
21+
title="Create an issue and assign it to someone, even yourself"
22+
description="Think of issues as jobs, tasks, work, or JTBD. Which we like.An issue and its sub-issues are usually time-based actionables assigned to members of your team. Your team creates, assigns, and completes issues to move your project towards its goal."
2223
image={emptyIssue}
24+
comicBox={{
25+
title: "Issues are building blocks in Plane.",
26+
direction: "left",
27+
description:
28+
"Redesign the Plane UI, Rebrand the company, or Launch the new fuel injection system are examples of issues that likely have sub-issues.",
29+
}}
2330
primaryButton={{
24-
text: "New issue",
31+
text: "Create your first issue",
2532
icon: <PlusIcon className="h-3 w-3" strokeWidth={2} />,
2633
onClick: () => {
2734
setTrackElement("PROJECT_EMPTY_STATE");

web/components/issues/issue-layouts/roots/cycle-layout-root.tsx

+17-14
Original file line numberDiff line numberDiff line change
@@ -68,20 +68,23 @@ export const CycleLayoutRoot: React.FC = observer(() => {
6868
</div>
6969
) : (
7070
<>
71-
{/* <CycleEmptyState workspaceSlug={workspaceSlug} projectId={projectId} cycleId={cycleId} /> */}
72-
<div className="h-full w-full overflow-auto">
73-
{activeLayout === "list" ? (
74-
<CycleListLayout />
75-
) : activeLayout === "kanban" ? (
76-
<CycleKanBanLayout />
77-
) : activeLayout === "calendar" ? (
78-
<CycleCalendarLayout />
79-
) : activeLayout === "gantt_chart" ? (
80-
<CycleGanttLayout />
81-
) : activeLayout === "spreadsheet" ? (
82-
<CycleSpreadsheetLayout />
83-
) : null}
84-
</div>
71+
{Object.keys(getIssues ?? {}).length == 0 ? (
72+
<CycleEmptyState workspaceSlug={workspaceSlug} projectId={projectId} cycleId={cycleId} />
73+
) : (
74+
<div className="h-full w-full overflow-auto">
75+
{activeLayout === "list" ? (
76+
<CycleListLayout />
77+
) : activeLayout === "kanban" ? (
78+
<CycleKanBanLayout />
79+
) : activeLayout === "calendar" ? (
80+
<CycleCalendarLayout />
81+
) : activeLayout === "gantt_chart" ? (
82+
<CycleGanttLayout />
83+
) : activeLayout === "spreadsheet" ? (
84+
<CycleSpreadsheetLayout />
85+
) : null}
86+
</div>
87+
)}
8588
</>
8689
)}
8790
</div>

web/components/issues/issue-layouts/roots/module-layout-root.tsx

+17-13
Original file line numberDiff line numberDiff line change
@@ -53,20 +53,24 @@ export const ModuleLayoutRoot: React.FC = observer(() => {
5353
</div>
5454
) : (
5555
<>
56+
{Object.keys(getIssues ?? {}).length == 0 ? (
57+
<ModuleEmptyState workspaceSlug={workspaceSlug} projectId={projectId} moduleId={moduleId} />
58+
) : (
59+
<div className="h-full w-full overflow-auto">
60+
{activeLayout === "list" ? (
61+
<ModuleListLayout />
62+
) : activeLayout === "kanban" ? (
63+
<ModuleKanBanLayout />
64+
) : activeLayout === "calendar" ? (
65+
<ModuleCalendarLayout />
66+
) : activeLayout === "gantt_chart" ? (
67+
<ModuleGanttLayout />
68+
) : activeLayout === "spreadsheet" ? (
69+
<ModuleSpreadsheetLayout />
70+
) : null}
71+
</div>
72+
)}
5673
{/* <ModuleEmptyState workspaceSlug={workspaceSlug} projectId={projectId} moduleId={moduleId} /> */}
57-
<div className="h-full w-full overflow-auto">
58-
{activeLayout === "list" ? (
59-
<ModuleListLayout />
60-
) : activeLayout === "kanban" ? (
61-
<ModuleKanBanLayout />
62-
) : activeLayout === "calendar" ? (
63-
<ModuleCalendarLayout />
64-
) : activeLayout === "gantt_chart" ? (
65-
<ModuleGanttLayout />
66-
) : activeLayout === "spreadsheet" ? (
67-
<ModuleSpreadsheetLayout />
68-
) : null}
69-
</div>
7074
</>
7175
)}
7276
</div>

web/components/issues/issue-layouts/roots/project-layout-root.tsx

+17-14
Original file line numberDiff line numberDiff line change
@@ -53,20 +53,23 @@ export const ProjectLayoutRoot: React.FC = observer(() => {
5353
</div>
5454
) : (
5555
<>
56-
{/* {(activeLayout === "list" || activeLayout === "spreadsheet") && issueCount === 0 && <ProjectEmptyState />} */}
57-
<div className="w-full h-full relative overflow-auto">
58-
{activeLayout === "list" ? (
59-
<ListLayout />
60-
) : activeLayout === "kanban" ? (
61-
<KanBanLayout />
62-
) : activeLayout === "calendar" ? (
63-
<CalendarLayout />
64-
) : activeLayout === "gantt_chart" ? (
65-
<GanttLayout />
66-
) : activeLayout === "spreadsheet" ? (
67-
<ProjectSpreadsheetLayout />
68-
) : null}
69-
</div>
56+
{Object.keys(getIssues ?? {}).length == 0 ? (
57+
<ProjectEmptyState />
58+
) : (
59+
<div className="w-full h-full relative overflow-auto">
60+
{activeLayout === "list" ? (
61+
<ListLayout />
62+
) : activeLayout === "kanban" ? (
63+
<KanBanLayout />
64+
) : activeLayout === "calendar" ? (
65+
<CalendarLayout />
66+
) : activeLayout === "gantt_chart" ? (
67+
<GanttLayout />
68+
) : activeLayout === "spreadsheet" ? (
69+
<ProjectSpreadsheetLayout />
70+
) : null}
71+
</div>
72+
)}
7073
</>
7174
)}
7275
</div>

web/components/modules/modules-list-view.tsx

+12-5
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,8 @@ import { EmptyState } from "components/common";
1111
// ui
1212
import { Loader } from "@plane/ui";
1313
// assets
14-
import emptyModule from "public/empty-state/module.svg";
14+
import emptyModule from "public/empty-state/empty_modules.webp";
15+
import { NewEmptyState } from "components/common/new-empty-state";
1516

1617
export const ModulesListView: React.FC = observer(() => {
1718
const router = useRouter();
@@ -78,13 +79,19 @@ export const ModulesListView: React.FC = observer(() => {
7879
{modulesView === "gantt_chart" && <ModulesListGanttChartView />}
7980
</>
8081
) : (
81-
<EmptyState
82-
title="Manage your project with modules"
83-
description="Modules are smaller, focused projects that help you group and organize issues."
82+
<NewEmptyState
83+
title="Map your project milestones to Modules and track aggregated work easily."
84+
description="A group of issues that belong to a logical, hierarchical parent form a module. Think of them as a way to track work by project milestones. They have their own periods and deadlines as well as analytics to help you see how close or far you are from a milestone."
8485
image={emptyModule}
86+
comicBox={{
87+
title: "Modules help group work by hierarchy.",
88+
direction: "right",
89+
description:
90+
"A cart module, a chassis module, and a warehouse module are all good example of this grouping.",
91+
}}
8592
primaryButton={{
8693
icon: <Plus className="h-4 w-4" />,
87-
text: "New Module",
94+
text: "Build your first module",
8895
onClick: () => commandPaletteStore.toggleCreateModuleModal(true),
8996
}}
9097
/>

web/components/page-views/workspace-dashboard.tsx

+25-17
Original file line numberDiff line numberDiff line change
@@ -12,14 +12,21 @@ import { CompletedIssuesGraph, IssuesList, IssuesPieChart, IssuesStats } from "c
1212
import { Button } from "@plane/ui";
1313
// images
1414
import emptyDashboard from "public/empty-state/dashboard.svg";
15+
import { NewEmptyState } from "components/common/new-empty-state";
16+
import emptyProject from "public/empty-state/dashboard_empty_project.webp";
1517

1618
export const WorkspaceDashboardView = observer(() => {
1719
// router
1820
const router = useRouter();
1921
const { workspaceSlug } = router.query;
2022
// store
2123

22-
const { user: userStore, project: projectStore, commandPalette: commandPaletteStore, trackEvent: { setTrackElement } } = useMobxStore();
24+
const {
25+
user: userStore,
26+
project: projectStore,
27+
commandPalette: commandPaletteStore,
28+
trackEvent: { setTrackElement },
29+
} = useMobxStore();
2330

2431
const user = userStore.currentUser;
2532
const projects = workspaceSlug ? projectStore.projects[workspaceSlug.toString()] : null;
@@ -65,22 +72,23 @@ export const WorkspaceDashboardView = observer(() => {
6572
</div>
6673
</div>
6774
) : (
68-
<div className="bg-custom-primary-100/5 flex justify-between gap-5 md:gap-8">
69-
<div className="p-5 md:p-8 pr-0">
70-
<h5 className="text-xl font-semibold">Create a project</h5>
71-
<p className="mt-2 mb-5">Manage your projects by creating issues, cycles, modules, views and pages.</p>
72-
<Button variant="primary" size="sm" onClick={() => {
73-
setTrackElement("DASHBOARD_PAGE");
74-
commandPaletteStore.toggleCreateProjectModal(true)
75-
}
76-
}>
77-
Create Project
78-
</Button>
79-
</div>
80-
<div className="hidden md:block self-end overflow-hidden pt-8">
81-
<Image src={emptyDashboard} alt="Empty Dashboard" />
82-
</div>
83-
</div>
75+
<NewEmptyState
76+
image={emptyProject}
77+
title="Overview of your projects, activity, and metrics"
78+
description="When you have created a project and have issues assigned, you will see metrics, activity, and things you care about here. This is personalized to your role in projects, so project admins will see more than members."
79+
comicBox={{
80+
title: "Everything starts with a project in Plane",
81+
direction: "right",
82+
description: "A project could be a product’s roadmap, a marketing campaign, or launching a new car.",
83+
}}
84+
primaryButton={{
85+
text: "Build your first project",
86+
onClick: () => {
87+
setTrackElement("DASHBOARD_PAGE");
88+
commandPaletteStore.toggleCreateProjectModal(true);
89+
},
90+
}}
91+
/>
8492
)
8593
) : null}
8694
</div>

0 commit comments

Comments
 (0)