Skip to content

Commit

Permalink
chore: empty state revamp and loader improvement (#3448)
Browse files Browse the repository at this point in the history
* chore: empty state asset added

* chore: empty state asset updated and image path helper function added

* chore: empty state asset updated

* chore: empty state asset updated and empty state details constant added

* chore: empty state component, helper function and comicbox button added

* chore: draft, archived and project issue empty state

* chore: cycle, module and issue layout empty state

* chore: analytics, dashboard, all issues, pages and project view empty state

* chore:projects empty state

* chore:projects empty state improvement

* chore: cycle, module, view and page loader improvement

* chore: code refactor
  • Loading branch information
anmolsinghbhatia authored Jan 24, 2024
1 parent 1a1594e commit 87f39d7
Show file tree
Hide file tree
Showing 105 changed files with 897 additions and 285 deletions.
39 changes: 13 additions & 26 deletions web/components/cycles/active-cycle-details.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import Link from "next/link";
import { observer } from "mobx-react-lite";
import useSWR from "swr";
// hooks
import { useApplication, useCycle, useIssues, useProject } from "hooks/store";
import { useCycle, useIssues, useProject, useUser } from "hooks/store";
import useToast from "hooks/use-toast";
// ui
import { SingleProgressStats } from "components/core";
Expand All @@ -22,6 +22,7 @@ import {
import ProgressChart from "components/core/sidebar/progress-chart";
import { ActiveCycleProgressStats } from "components/cycles";
import { StateDropdown } from "components/dropdowns";
import { EmptyState, getEmptyStateImagePath } from "components/empty-state";
// icons
import { ArrowRight, CalendarCheck, CalendarDays, Star, Target } from "lucide-react";
// helpers
Expand All @@ -32,7 +33,7 @@ import { ICycle, TCycleGroups } from "@plane/types";
// constants
import { EIssuesStoreType } from "constants/issue";
import { CYCLE_ISSUES_WITH_PARAMS } from "constants/fetch-keys";
import { CYCLE_STATE_GROUPS_DETAILS } from "constants/cycle";
import { CYCLE_EMPTY_STATE_DETAILS, CYCLE_STATE_GROUPS_DETAILS } from "constants/cycle";

interface IActiveCycleDetails {
workspaceSlug: string;
Expand All @@ -43,12 +44,10 @@ export const ActiveCycleDetails: React.FC<IActiveCycleDetails> = observer((props
// props
const { workspaceSlug, projectId } = props;
// store hooks
const { currentUser } = useUser();
const {
issues: { fetchActiveCycleIssues },
} = useIssues(EIssuesStoreType.CYCLE);
const {
commandPalette: { toggleCreateCycleModal },
} = useApplication();
const {
fetchActiveCycle,
currentProjectActiveCycleId,
Expand Down Expand Up @@ -76,6 +75,9 @@ export const ActiveCycleDetails: React.FC<IActiveCycleDetails> = observer((props
: null
);

const emptyStateDetail = CYCLE_EMPTY_STATE_DETAILS["active"];
const emptyStateImage = getEmptyStateImagePath("cycle", "active", currentUser?.theme.theme === "light");

if (!activeCycle && isLoading)
return (
<Loader>
Expand All @@ -85,27 +87,12 @@ export const ActiveCycleDetails: React.FC<IActiveCycleDetails> = observer((props

if (!activeCycle)
return (
<div className="grid h-full place-items-center text-center">
<div className="space-y-2">
<div className="mx-auto flex justify-center">
<svg xmlns="http://www.w3.org/2000/svg" width="66" height="66" viewBox="0 0 66 66" fill="none">
<circle cx="34.375" cy="34.375" r="22" stroke="rgb(var(--color-text-400))" strokeLinecap="round" />
<path
d="M36.4375 20.9919C36.4375 19.2528 37.6796 17.8127 39.1709 18.1419C40.125 18.3526 41.0604 18.6735 41.9625 19.1014C43.7141 19.9322 45.3057 21.1499 46.6464 22.685C47.987 24.2202 49.0505 26.0426 49.776 28.0484C50.5016 30.0541 50.875 32.2038 50.875 34.3748C50.875 36.5458 50.5016 38.6956 49.776 40.7013C49.0505 42.7071 47.987 44.5295 46.6464 46.0647C45.3057 47.5998 43.7141 48.8175 41.9625 49.6483C41.0604 50.0762 40.125 50.3971 39.1709 50.6077C37.6796 50.937 36.4375 49.4969 36.4375 47.7578L36.4375 20.9919Z"
fill="rgb(var(--color-text-400))"
/>
</svg>
</div>
<h4 className="text-sm text-custom-text-200">No active cycle</h4>
<button
type="button"
className="text-sm text-custom-primary-100 outline-none"
onClick={() => toggleCreateCycleModal(true)}
>
Create a new cycle
</button>
</div>
</div>
<EmptyState
title={emptyStateDetail.title}
description={emptyStateDetail.description}
image={emptyStateImage}
size="sm"
/>
);

const endDate = new Date(activeCycle.end_date ?? "");
Expand Down
37 changes: 14 additions & 23 deletions web/components/cycles/cycles-board.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
import { FC } from "react";
import { observer } from "mobx-react-lite";
// hooks
import { useApplication } from "hooks/store";
import { useUser } from "hooks/store";
// components
import { CyclePeekOverview, CyclesBoardCard } from "components/cycles";
import { EmptyState, getEmptyStateImagePath } from "components/empty-state";
// constants
import { CYCLE_EMPTY_STATE_DETAILS } from "constants/cycle";

export interface ICyclesBoard {
cycleIds: string[];
Expand All @@ -16,7 +19,10 @@ export interface ICyclesBoard {
export const CyclesBoard: FC<ICyclesBoard> = observer((props) => {
const { cycleIds, filter, workspaceSlug, projectId, peekCycle } = props;
// store hooks
const { commandPalette: commandPaletteStore } = useApplication();
const { currentUser } = useUser();

const emptyStateDetail = CYCLE_EMPTY_STATE_DETAILS[filter as keyof typeof CYCLE_EMPTY_STATE_DETAILS];
const emptyStateImage = getEmptyStateImagePath("cycle", filter, currentUser?.theme.theme === "light");

return (
<>
Expand All @@ -41,27 +47,12 @@ export const CyclesBoard: FC<ICyclesBoard> = observer((props) => {
</div>
</div>
) : (
<div className="grid h-full place-items-center text-center">
<div className="space-y-2">
<div className="mx-auto flex justify-center">
<svg xmlns="http://www.w3.org/2000/svg" width="66" height="66" viewBox="0 0 66 66" fill="none">
<circle cx="34.375" cy="34.375" r="22" stroke="rgb(var(--color-text-400))" strokeLinecap="round" />
<path
d="M36.4375 20.9919C36.4375 19.2528 37.6796 17.8127 39.1709 18.1419C40.125 18.3526 41.0604 18.6735 41.9625 19.1014C43.7141 19.9322 45.3057 21.1499 46.6464 22.685C47.987 24.2202 49.0505 26.0426 49.776 28.0484C50.5016 30.0541 50.875 32.2038 50.875 34.3748C50.875 36.5458 50.5016 38.6956 49.776 40.7013C49.0505 42.7071 47.987 44.5295 46.6464 46.0647C45.3057 47.5998 43.7141 48.8175 41.9625 49.6483C41.0604 50.0762 40.125 50.3971 39.1709 50.6077C37.6796 50.937 36.4375 49.4969 36.4375 47.7578L36.4375 20.9919Z"
fill="rgb(var(--color-text-400))"
/>
</svg>
</div>
<h4 className="text-sm text-custom-text-200">{filter === "all" ? "No cycles" : `No ${filter} cycles`}</h4>
<button
type="button"
className="text-sm text-custom-primary-100 outline-none"
onClick={() => commandPaletteStore.toggleCreateCycleModal(true)}
>
Create a new cycle
</button>
</div>
</div>
<EmptyState
title={emptyStateDetail.title}
description={emptyStateDetail.description}
image={emptyStateImage}
size="sm"
/>
)}
</>
);
Expand Down
45 changes: 14 additions & 31 deletions web/components/cycles/cycles-list.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,14 @@
import { FC } from "react";
import { observer } from "mobx-react-lite";
// hooks
import { useApplication } from "hooks/store";
import { useUser } from "hooks/store";
// components
import { CyclePeekOverview, CyclesListItem } from "components/cycles";
import { EmptyState, getEmptyStateImagePath } from "components/empty-state";
// ui
import { Loader } from "@plane/ui";
// constants
import { CYCLE_EMPTY_STATE_DETAILS } from "constants/cycle";

export interface ICyclesList {
cycleIds: string[];
Expand All @@ -17,10 +20,10 @@ export interface ICyclesList {
export const CyclesList: FC<ICyclesList> = observer((props) => {
const { cycleIds, filter, workspaceSlug, projectId } = props;
// store hooks
const {
commandPalette: commandPaletteStore,
eventTracker: { setTrackElement },
} = useApplication();
const { currentUser } = useUser();

const emptyStateDetail = CYCLE_EMPTY_STATE_DETAILS[filter as keyof typeof CYCLE_EMPTY_STATE_DETAILS];
const emptyStateImage = getEmptyStateImagePath("cycle", filter, currentUser?.theme.theme === "light");

return (
<>
Expand All @@ -46,32 +49,12 @@ export const CyclesList: FC<ICyclesList> = observer((props) => {
</div>
</div>
) : (
<div className="grid h-full place-items-center text-center">
<div className="space-y-2">
<div className="mx-auto flex justify-center">
<svg xmlns="http://www.w3.org/2000/svg" width="66" height="66" viewBox="0 0 66 66" fill="none">
<circle cx="34.375" cy="34.375" r="22" stroke="rgb(var(--color-text-400))" strokeLinecap="round" />
<path
d="M36.4375 20.9919C36.4375 19.2528 37.6796 17.8127 39.1709 18.1419C40.125 18.3526 41.0604 18.6735 41.9625 19.1014C43.7141 19.9322 45.3057 21.1499 46.6464 22.685C47.987 24.2202 49.0505 26.0426 49.776 28.0484C50.5016 30.0541 50.875 32.2038 50.875 34.3748C50.875 36.5458 50.5016 38.6956 49.776 40.7013C49.0505 42.7071 47.987 44.5295 46.6464 46.0647C45.3057 47.5998 43.7141 48.8175 41.9625 49.6483C41.0604 50.0762 40.125 50.3971 39.1709 50.6077C37.6796 50.937 36.4375 49.4969 36.4375 47.7578L36.4375 20.9919Z"
fill="rgb(var(--color-text-400))"
/>
</svg>
</div>
<h4 className="text-sm text-custom-text-200">
{filter === "all" ? "No cycles" : `No ${filter} cycles`}
</h4>
<button
type="button"
className="text-sm text-custom-primary-100 outline-none"
onClick={() => {
setTrackElement("CYCLES_PAGE_EMPTY-STATE");
commandPaletteStore.toggleCreateCycleModal(true);
}}
>
Create a new cycle
</button>
</div>
</div>
<EmptyState
title={emptyStateDetail.title}
description={emptyStateDetail.description}
image={emptyStateImage}
size="sm"
/>
)}
</>
) : (
Expand Down
75 changes: 75 additions & 0 deletions web/components/empty-state/comic-box-button.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
import { useState } from "react";
import { Popover } from "@headlessui/react";
// popper
import { usePopper } from "react-popper";
// helper
import { getButtonStyling } from "@plane/ui";

type Props = {
label: string;
icon?: any;
title: string | undefined;
description: string | undefined;
onClick?: () => void;
disabled?: boolean;
};

export const ComicBoxButton: React.FC<Props> = (props) => {
const { label, icon, title, description, onClick, disabled = false } = props;
const [isHovered, setIsHovered] = useState(false);

const handleMouseEnter = () => {
setIsHovered(true);
};

const handleMouseLeave = () => {
setIsHovered(false);
};

const [referenceElement, setReferenceElement] = useState<HTMLButtonElement | null>(null);
const [popperElement, setPopperElement] = useState<HTMLDivElement | null>();
const { styles, attributes } = usePopper(referenceElement, popperElement, {
placement: "right-end",
modifiers: [
{
name: "offset",
options: {
offset: [0, 10],
},
},
],
});

return (
<Popover>
<Popover.Button ref={setReferenceElement} onClick={onClick} disabled={disabled}>
<div className={`flex items-center gap-2.5 ${getButtonStyling("primary", "lg", disabled)}`}>
{icon}
<span className="leading-4">{label}</span>
<span className="relative h-2 w-2">
<div
onMouseEnter={handleMouseEnter}
onMouseLeave={handleMouseLeave}
className={`absolute bg-blue-300 right-0 z-10 h-2.5 w-2.5 animate-ping rounded-full`}
/>
<div className={`absolute bg-blue-400/40 right-0 h-1.5 w-1.5 mt-0.5 mr-0.5 rounded-full`} />
</span>
</div>
</Popover.Button>
{isHovered && (
<Popover.Panel
as="div"
className="flex flex-col rounded border border-custom-border-200 bg-custom-background-100 p-5 relative min-w-80"
ref={setPopperElement}
style={styles.popper}
{...attributes.popper}
static
>
<div className="absolute w-2 h-2 bg-custom-background-100 border rounded-lb-sm border-custom-border-200 border-r-0 border-t-0 transform rotate-45 bottom-2 -left-[5px]" />
<h3 className="text-lg font-semibold w-full">{title}</h3>
<h4 className="mt-1 text-sm">{description}</h4>
</Popover.Panel>
)}
</Popover>
);
};
Loading

0 comments on commit 87f39d7

Please sign in to comment.