mirror of
https://github.com/makeplane/plane
synced 2024-06-14 14:31:34 +00:00
style: empty state global component (#435)
This commit is contained in:
parent
bfab4865cd
commit
636e8e6c60
@ -14,7 +14,9 @@ import { CompletedCycleIcon } from "components/icons";
|
|||||||
import { ICycle, SelectCycleType } from "types";
|
import { ICycle, SelectCycleType } from "types";
|
||||||
// fetch-keys
|
// fetch-keys
|
||||||
import { CYCLE_COMPLETE_LIST } from "constants/fetch-keys";
|
import { CYCLE_COMPLETE_LIST } from "constants/fetch-keys";
|
||||||
import { Loader } from "components/ui";
|
import { EmptyState, Loader } from "components/ui";
|
||||||
|
// image
|
||||||
|
import emptyCycle from "public/empty-state/empty-cycle.svg";
|
||||||
|
|
||||||
export interface CompletedCyclesListProps {
|
export interface CompletedCyclesListProps {
|
||||||
setCreateUpdateCycleModal: React.Dispatch<React.SetStateAction<boolean>>;
|
setCreateUpdateCycleModal: React.Dispatch<React.SetStateAction<boolean>>;
|
||||||
@ -72,13 +74,13 @@ export const CompletedCyclesList: React.FC<CompletedCyclesListProps> = ({
|
|||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<div className="flex flex-col items-center justify-center gap-4 text-center">
|
<EmptyState
|
||||||
<CompletedCycleIcon height="56" width="56" />
|
type="cycle"
|
||||||
<h3 className="text-gray-500">
|
title="Create New Cycle"
|
||||||
No completed cycles yet. Create with{" "}
|
description="Sprint more effectively with Cycles by confining your project
|
||||||
<pre className="inline rounded bg-gray-200 px-2 py-1">Q</pre>.
|
to a fixed amount of time. Create new cycle now."
|
||||||
</h3>
|
imgURL={emptyCycle}
|
||||||
</div>
|
/>
|
||||||
)
|
)
|
||||||
) : (
|
) : (
|
||||||
<Loader className="grid grid-cols-1 gap-9 md:grid-cols-2 lg:grid-cols-3">
|
<Loader className="grid grid-cols-1 gap-9 md:grid-cols-2 lg:grid-cols-3">
|
||||||
|
@ -1,8 +1,11 @@
|
|||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
|
|
||||||
// components
|
// components
|
||||||
import { DeleteCycleModal, EmptyCycle, SingleCycleCard } from "components/cycles";
|
import { DeleteCycleModal, SingleCycleCard } from "components/cycles";
|
||||||
import { Loader } from "components/ui";
|
import { EmptyState, Loader } from "components/ui";
|
||||||
|
// image
|
||||||
|
import emptyCycle from "public/empty-state/empty-cycle.svg";
|
||||||
|
|
||||||
// types
|
// types
|
||||||
import { ICycle, SelectCycleType } from "types";
|
import { ICycle, SelectCycleType } from "types";
|
||||||
|
|
||||||
@ -56,7 +59,13 @@ export const CyclesList: React.FC<TCycleStatsViewProps> = ({
|
|||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<EmptyCycle />
|
<EmptyState
|
||||||
|
type="cycle"
|
||||||
|
title="Create New Cycle"
|
||||||
|
description="Sprint more effectively with Cycles by confining your project
|
||||||
|
to a fixed amount of time. Create new cycle now."
|
||||||
|
imgURL={emptyCycle}
|
||||||
|
/>
|
||||||
)
|
)
|
||||||
) : (
|
) : (
|
||||||
<Loader className="grid grid-cols-1 gap-9 md:grid-cols-2 lg:grid-cols-3">
|
<Loader className="grid grid-cols-1 gap-9 md:grid-cols-2 lg:grid-cols-3">
|
||||||
|
59
apps/app/components/ui/empty-state.tsx
Normal file
59
apps/app/components/ui/empty-state.tsx
Normal file
@ -0,0 +1,59 @@
|
|||||||
|
import React from "react";
|
||||||
|
import Image from "next/image";
|
||||||
|
|
||||||
|
// icon
|
||||||
|
import { PlusIcon } from "@heroicons/react/24/outline";
|
||||||
|
// helper
|
||||||
|
import { capitalizeFirstLetter } from "helpers/string.helper";
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
type: "cycle" | "module" | "project" | "issue";
|
||||||
|
title: string;
|
||||||
|
description: React.ReactNode | string;
|
||||||
|
imgURL: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const EmptyState: React.FC<Props> = ({ type, title, description, imgURL }) => {
|
||||||
|
const shortcutKey = (type: string) => {
|
||||||
|
switch (type) {
|
||||||
|
case "cycle":
|
||||||
|
return "Q";
|
||||||
|
case "module":
|
||||||
|
return "M";
|
||||||
|
case "project":
|
||||||
|
return "P";
|
||||||
|
default:
|
||||||
|
return "C";
|
||||||
|
}
|
||||||
|
};
|
||||||
|
return (
|
||||||
|
<div className="flex h-full w-full flex-col items-center justify-center gap-5 text-center">
|
||||||
|
<div className="h-32 w-72">
|
||||||
|
<Image src={imgURL} height="128" width="288" alt={type} />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h3 className="text-xl font-semibold">{title}</h3>
|
||||||
|
<span>
|
||||||
|
Use shortcut{" "}
|
||||||
|
<span className="rounded-sm mx-1 border border-gray-200 bg-gray-100 px-2 py-1 text-sm font-medium text-gray-800">
|
||||||
|
{shortcutKey(type)}
|
||||||
|
</span>{" "}
|
||||||
|
to create {type} from anywhere.
|
||||||
|
</span>
|
||||||
|
<p className="max-w-md text-sm text-gray-500">{description}</p>
|
||||||
|
|
||||||
|
<button
|
||||||
|
className="flex items-center gap-1 rounded-lg bg-theme px-2.5 py-2 text-sm text-white"
|
||||||
|
onClick={() => {
|
||||||
|
const e = new KeyboardEvent("keydown", {
|
||||||
|
key: shortcutKey(type),
|
||||||
|
});
|
||||||
|
document.dispatchEvent(e);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<PlusIcon className="h-4 w-4 font-bold text-white" />
|
||||||
|
Create New {capitalizeFirstLetter(type)}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
@ -18,3 +18,4 @@ export * from "./spinner";
|
|||||||
export * from "./tooltip";
|
export * from "./tooltip";
|
||||||
export * from "./labels-list";
|
export * from "./labels-list";
|
||||||
export * from "./linear-progress-indicator";
|
export * from "./linear-progress-indicator";
|
||||||
|
export * from "./empty-state";
|
@ -14,7 +14,7 @@ import { IssueViewContextProvider } from "contexts/issue-view.context";
|
|||||||
// components
|
// components
|
||||||
import { IssuesFilterView, IssuesView } from "components/core";
|
import { IssuesFilterView, IssuesView } from "components/core";
|
||||||
// ui
|
// ui
|
||||||
import { Spinner, EmptySpace, EmptySpaceItem, HeaderButton } from "components/ui";
|
import { Spinner, EmptySpace, EmptySpaceItem, HeaderButton, EmptyState } from "components/ui";
|
||||||
import { BreadcrumbItem, Breadcrumbs } from "components/breadcrumbs";
|
import { BreadcrumbItem, Breadcrumbs } from "components/breadcrumbs";
|
||||||
// icons
|
// icons
|
||||||
import { RectangleStackIcon, PlusIcon } from "@heroicons/react/24/outline";
|
import { RectangleStackIcon, PlusIcon } from "@heroicons/react/24/outline";
|
||||||
@ -23,6 +23,8 @@ import type { UserAuth } from "types";
|
|||||||
import type { GetServerSidePropsContext, NextPage } from "next";
|
import type { GetServerSidePropsContext, NextPage } from "next";
|
||||||
// fetch-keys
|
// fetch-keys
|
||||||
import { PROJECT_DETAILS, PROJECT_ISSUES_LIST } from "constants/fetch-keys";
|
import { PROJECT_DETAILS, PROJECT_ISSUES_LIST } from "constants/fetch-keys";
|
||||||
|
// image
|
||||||
|
import emptyIssue from "public/empty-state/empty-issue.svg";
|
||||||
|
|
||||||
const ProjectIssues: NextPage<UserAuth> = (props) => {
|
const ProjectIssues: NextPage<UserAuth> = (props) => {
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
@ -76,30 +78,13 @@ const ProjectIssues: NextPage<UserAuth> = (props) => {
|
|||||||
userAuth={props}
|
userAuth={props}
|
||||||
/>
|
/>
|
||||||
) : (
|
) : (
|
||||||
<div className="grid h-full w-full place-items-center px-4 sm:px-0">
|
<EmptyState
|
||||||
<EmptySpace
|
type="issue"
|
||||||
title="You don't have any issue yet."
|
title="Create New Issue"
|
||||||
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."
|
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.
|
||||||
Icon={RectangleStackIcon}
|
Create a new issue"
|
||||||
>
|
imgURL={emptyIssue}
|
||||||
<EmptySpaceItem
|
/>
|
||||||
title="Create a new issue"
|
|
||||||
description={
|
|
||||||
<span>
|
|
||||||
Use <pre className="inline rounded bg-gray-200 px-2 py-1">C</pre> shortcut to
|
|
||||||
create a new issue
|
|
||||||
</span>
|
|
||||||
}
|
|
||||||
Icon={PlusIcon}
|
|
||||||
action={() => {
|
|
||||||
const e = new KeyboardEvent("keydown", {
|
|
||||||
key: "c",
|
|
||||||
});
|
|
||||||
document.dispatchEvent(e);
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</EmptySpace>
|
|
||||||
</div>
|
|
||||||
)
|
)
|
||||||
) : (
|
) : (
|
||||||
<div className="flex h-full w-full items-center justify-center">
|
<div className="flex h-full w-full items-center justify-center">
|
||||||
|
@ -2,7 +2,10 @@ import React, { useEffect, useState } from "react";
|
|||||||
|
|
||||||
import { useRouter } from "next/router";
|
import { useRouter } from "next/router";
|
||||||
import useSWR from "swr";
|
import useSWR from "swr";
|
||||||
import { PlusIcon, RectangleGroupIcon } from "@heroicons/react/24/outline";
|
import { PlusIcon } from "@heroicons/react/24/outline";
|
||||||
|
// image
|
||||||
|
import emptyModule from "public/empty-state/empty-module.svg";
|
||||||
|
|
||||||
|
|
||||||
// layouts
|
// layouts
|
||||||
import AppLayout from "layouts/app-layout";
|
import AppLayout from "layouts/app-layout";
|
||||||
@ -14,7 +17,7 @@ import modulesService from "services/modules.service";
|
|||||||
// components
|
// components
|
||||||
import { CreateUpdateModuleModal, SingleModuleCard } from "components/modules";
|
import { CreateUpdateModuleModal, SingleModuleCard } from "components/modules";
|
||||||
// ui
|
// ui
|
||||||
import { EmptySpace, EmptySpaceItem, HeaderButton, Loader } from "components/ui";
|
import { EmptyState, HeaderButton, Loader } from "components/ui";
|
||||||
import { BreadcrumbItem, Breadcrumbs } from "components/breadcrumbs";
|
import { BreadcrumbItem, Breadcrumbs } from "components/breadcrumbs";
|
||||||
// icons
|
// icons
|
||||||
// types
|
// types
|
||||||
@ -103,30 +106,12 @@ const ProjectModules: NextPage = () => {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<div className="flex h-full w-full flex-col items-center justify-center px-4">
|
<EmptyState
|
||||||
<EmptySpace
|
type="module"
|
||||||
title="You don't have any module yet."
|
title="Create New Module"
|
||||||
description="Modules are smaller, focused projects that help you group and organize issues within a specific time frame."
|
description="Modules are smaller, focused projects that help you group and organize issues within a specific time frame."
|
||||||
Icon={RectangleGroupIcon}
|
imgURL={emptyModule}
|
||||||
>
|
/>
|
||||||
<EmptySpaceItem
|
|
||||||
title="Create a new module"
|
|
||||||
description={
|
|
||||||
<span>
|
|
||||||
Use <pre className="inline rounded bg-gray-200 px-2 py-1">M</pre> shortcut to
|
|
||||||
create a new module
|
|
||||||
</span>
|
|
||||||
}
|
|
||||||
Icon={PlusIcon}
|
|
||||||
action={() => {
|
|
||||||
const e = new KeyboardEvent("keydown", {
|
|
||||||
key: "m",
|
|
||||||
});
|
|
||||||
document.dispatchEvent(e);
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</EmptySpace>
|
|
||||||
</div>
|
|
||||||
)
|
)
|
||||||
) : (
|
) : (
|
||||||
<Loader className="grid grid-cols-3 gap-4">
|
<Loader className="grid grid-cols-3 gap-4">
|
||||||
|
@ -14,14 +14,16 @@ import AppLayout from "layouts/app-layout";
|
|||||||
import { JoinProjectModal } from "components/project/join-project-modal";
|
import { JoinProjectModal } from "components/project/join-project-modal";
|
||||||
import { DeleteProjectModal, SingleProjectCard } from "components/project";
|
import { DeleteProjectModal, SingleProjectCard } from "components/project";
|
||||||
// ui
|
// ui
|
||||||
import { HeaderButton, EmptySpace, EmptySpaceItem, Loader } from "components/ui";
|
import { HeaderButton, Loader, EmptyState } from "components/ui";
|
||||||
import { Breadcrumbs, BreadcrumbItem } from "components/breadcrumbs";
|
import { Breadcrumbs, BreadcrumbItem } from "components/breadcrumbs";
|
||||||
// icons
|
// icons
|
||||||
import { ClipboardDocumentListIcon, PlusIcon } from "@heroicons/react/24/outline";
|
import { PlusIcon } from "@heroicons/react/24/outline";
|
||||||
// types
|
// types
|
||||||
import type { GetServerSidePropsContext, NextPage } from "next";
|
import type { GetServerSidePropsContext, NextPage } from "next";
|
||||||
// fetch-keys
|
// fetch-keys
|
||||||
import { PROJECT_MEMBERS } from "constants/fetch-keys";
|
import { PROJECT_MEMBERS } from "constants/fetch-keys";
|
||||||
|
// image
|
||||||
|
import emptyProject from "public/empty-state/empty-project.svg";
|
||||||
|
|
||||||
const ProjectsPage: NextPage = () => {
|
const ProjectsPage: NextPage = () => {
|
||||||
// router
|
// router
|
||||||
@ -80,28 +82,12 @@ const ProjectsPage: NextPage = () => {
|
|||||||
{projects ? (
|
{projects ? (
|
||||||
<>
|
<>
|
||||||
{projects.length === 0 ? (
|
{projects.length === 0 ? (
|
||||||
<div className="grid h-full w-full place-items-center px-4 sm:px-0">
|
<EmptyState
|
||||||
<EmptySpace
|
type="project"
|
||||||
title="You don't have any project yet."
|
title="Create New Project"
|
||||||
description="Projects are a collection of issues. They can be used to represent the development work for a product, project, or service."
|
description="Projects are a collection of issues. They can be used to represent the development work for a product, project, or service."
|
||||||
Icon={ClipboardDocumentListIcon}
|
imgURL={emptyProject}
|
||||||
>
|
/>
|
||||||
<EmptySpaceItem
|
|
||||||
title="Create a new project"
|
|
||||||
description={
|
|
||||||
<span>
|
|
||||||
Use <pre className="inline rounded bg-gray-200 px-2 py-1">P</pre> shortcut to
|
|
||||||
create a new project
|
|
||||||
</span>
|
|
||||||
}
|
|
||||||
Icon={PlusIcon}
|
|
||||||
action={() => {
|
|
||||||
const e = new KeyboardEvent("keydown", { key: "p" });
|
|
||||||
document.dispatchEvent(e);
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</EmptySpace>
|
|
||||||
</div>
|
|
||||||
) : (
|
) : (
|
||||||
<div className="grid grid-cols-1 gap-9 md:grid-cols-2 lg:grid-cols-3">
|
<div className="grid grid-cols-1 gap-9 md:grid-cols-2 lg:grid-cols-3">
|
||||||
{projects.map((project) => (
|
{projects.map((project) => (
|
||||||
|
50
apps/app/public/empty-state/empty-cycle.svg
Normal file
50
apps/app/public/empty-state/empty-cycle.svg
Normal file
File diff suppressed because one or more lines are too long
After Width: | Height: | Size: 20 KiB |
62
apps/app/public/empty-state/empty-issue.svg
Normal file
62
apps/app/public/empty-state/empty-issue.svg
Normal file
File diff suppressed because one or more lines are too long
After Width: | Height: | Size: 19 KiB |
50
apps/app/public/empty-state/empty-module.svg
Normal file
50
apps/app/public/empty-state/empty-module.svg
Normal file
File diff suppressed because one or more lines are too long
After Width: | Height: | Size: 20 KiB |
56
apps/app/public/empty-state/empty-project.svg
Normal file
56
apps/app/public/empty-state/empty-project.svg
Normal file
File diff suppressed because one or more lines are too long
After Width: | Height: | Size: 24 KiB |
Loading…
Reference in New Issue
Block a user