[WEB-1093] chore: padding and borders consistency (#4315)

* chore: global list layout and list item component added

* chore: project view list layout consistency

* chore: project view sub header consistency

* chore: pages list layout consistency

* chore: project view sub header improvement

* chore: list layout item component improvement

* chore: module list layout consistency

* chore: cycle list layout consistency

* chore: issue list layout consistency

* chore: header height consistency

* chore: sub header consistency

* chore: list layout improvement

* chore: inbox sidebar improvement

* fix: cycle quick action

* chore: inbox selected issue improvement

* chore: label option removed from pages filter

* chore: inbox create issue modal improvement
This commit is contained in:
Anmol Singh Bhatia 2024-04-30 17:21:24 +05:30 committed by GitHub
parent 1b79517f07
commit 87a606446f
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
34 changed files with 1230 additions and 1081 deletions

View file

@ -14,7 +14,7 @@ import {
// helpers
import { calculateTotalFilters } from "@/helpers/filter.helper";
// hooks
import { useLabel, useMember, useProjectPages } from "@/hooks/store";
import { useMember, useProjectPages } from "@/hooks/store";
type Props = {
pageType: TPageNavigationTabs;
@ -29,7 +29,6 @@ export const PagesListHeaderRoot: React.FC<Props> = observer((props) => {
const {
workspace: { workspaceMemberIds },
} = useMember();
const { projectLabels } = useLabel();
const handleRemoveFilter = useCallback(
(key: keyof TPageFilterProps, value: string | null) => {
@ -48,7 +47,7 @@ export const PagesListHeaderRoot: React.FC<Props> = observer((props) => {
return (
<>
<div className="flex-shrink-0 w-full border-b border-custom-border-200 px-3 relative flex items-center gap-4 justify-between">
<div className="flex-shrink-0 h-[50px] w-full border-b border-custom-border-200 px-6 relative flex items-center gap-4 justify-between">
<PageTabNavigation workspaceSlug={workspaceSlug} projectId={projectId} pageType={pageType} />
<div className="h-full flex items-center gap-2 self-end">
<PageSearchInput projectId={projectId} />
@ -64,7 +63,6 @@ export const PagesListHeaderRoot: React.FC<Props> = observer((props) => {
<PageFiltersSelection
filters={filters}
handleFiltersUpdate={updateFilters}
labels={projectLabels}
memberIds={workspaceMemberIds ?? undefined}
/>
</FiltersDropdown>

View file

@ -0,0 +1,94 @@
import React, { FC } from "react";
import { observer } from "mobx-react";
import { Circle, Earth, Info, Lock, Minus } from "lucide-react";
// ui
import { Avatar, TOAST_TYPE, Tooltip, setToast } from "@plane/ui";
// components
import { FavoriteStar } from "@/components/core";
import { PageQuickActions } from "@/components/pages/dropdowns";
// helpers
import { renderFormattedDate } from "@/helpers/date-time.helper";
// hooks
import { useMember, usePage } from "@/hooks/store";
type Props = {
workspaceSlug: string;
projectId: string;
pageId: string;
};
export const BlockItemAction: FC<Props> = observer((props) => {
const { workspaceSlug, projectId, pageId } = props;
// store hooks
const { access, created_at, is_favorite, owned_by, addToFavorites, removeFromFavorites } = usePage(pageId);
const { getUserDetails } = useMember();
// derived values
const ownerDetails = owned_by ? getUserDetails(owned_by) : undefined;
// handlers
const handleFavorites = () => {
if (is_favorite)
removeFromFavorites().then(() =>
setToast({
type: TOAST_TYPE.SUCCESS,
title: "Success!",
message: "Page removed from favorites.",
})
);
else
addToFavorites().then(() =>
setToast({
type: TOAST_TYPE.SUCCESS,
title: "Success!",
message: "Page added to favorites.",
})
);
};
return (
<>
{/* page details */}
<div className="flex items-center gap-2 text-custom-text-400">
{/* <span className="text-xs">Labels</span>
<Circle className="h-1 w-1 fill-custom-text-300" /> */}
<div className="cursor-default">
<Tooltip tooltipHeading="Owned by" tooltipContent={ownerDetails?.display_name}>
<Avatar src={ownerDetails?.avatar} name={ownerDetails?.display_name} />
</Tooltip>
</div>
<Circle className="h-1 w-1 fill-custom-text-300" />
{/* <span className="text-xs cursor-default">10m read</span>
<Circle className="h-1 w-1 fill-custom-text-300" /> */}
<div className="cursor-default">
<Tooltip tooltipContent={access === 0 ? "Public" : "Private"}>
{access === 0 ? <Earth className="h-3 w-3" /> : <Lock className="h-3 w-3" />}
</Tooltip>
</div>
</div>
{/* vertical divider */}
<Minus className="h-5 w-5 text-custom-text-400 rotate-90 -mx-3" strokeWidth={1} />
{/* page info */}
<Tooltip tooltipContent={`Created on ${renderFormattedDate(created_at)}`}>
<span className="h-4 w-4 grid place-items-center cursor-default">
<Info className="h-4 w-4 text-custom-text-300" />
</span>
</Tooltip>
{/* favorite/unfavorite */}
<FavoriteStar
onClick={(e) => {
e.preventDefault();
e.stopPropagation();
handleFavorites();
}}
selected={is_favorite}
/>
{/* quick actions dropdown */}
<PageQuickActions pageId={pageId} projectId={projectId} workspaceSlug={workspaceSlug} />
</>
);
});

View file

@ -1,15 +1,11 @@
import { FC } from "react";
import { observer } from "mobx-react";
import Link from "next/link";
import { Circle, Info, Lock, Minus, UsersRound } from "lucide-react";
import { Avatar, TOAST_TYPE, Tooltip, setToast } from "@plane/ui";
// components
import { FavoriteStar } from "@/components/core";
import { PageQuickActions } from "@/components/pages";
// helpers
import { renderFormattedDate } from "@/helpers/date-time.helper";
import { ListItem } from "@/components/core/list";
import { BlockItemAction } from "@/components/pages/list";
// hooks
import { useMember, usePage } from "@/hooks/store";
import { usePage } from "@/hooks/store";
import { usePlatformOS } from "@/hooks/use-platform-os";
type TPageListBlock = {
workspaceSlug: string;
@ -20,84 +16,15 @@ type TPageListBlock = {
export const PageListBlock: FC<TPageListBlock> = observer((props) => {
const { workspaceSlug, projectId, pageId } = props;
// hooks
const { access, created_at, is_favorite, name, owned_by, addToFavorites, removeFromFavorites } = usePage(pageId);
const { getUserDetails } = useMember();
// derived values
const ownerDetails = owned_by ? getUserDetails(owned_by) : undefined;
const handleFavorites = () => {
if (is_favorite)
removeFromFavorites().then(() =>
setToast({
type: TOAST_TYPE.SUCCESS,
title: "Success!",
message: "Page removed from favorites.",
})
);
else
addToFavorites().then(() =>
setToast({
type: TOAST_TYPE.SUCCESS,
title: "Success!",
message: "Page added to favorites.",
})
);
};
const { name } = usePage(pageId);
const { isMobile } = usePlatformOS();
return (
<Link
href={`/${workspaceSlug}/projects/${projectId}/pages/${pageId}`}
className="flex items-center justify-between gap-5 py-7 px-6 hover:bg-custom-background-90"
>
{/* page title */}
<Tooltip tooltipHeading="Title" tooltipContent={name}>
<h5 className="text-base font-medium truncate">{name}</h5>
</Tooltip>
{/* page properties */}
<div className="flex items-center gap-5 flex-shrink-0">
{/* page details */}
<div className="flex items-center gap-2 text-custom-text-400">
{/* <span className="text-xs">Labels</span>
<Circle className="h-1 w-1 fill-custom-text-300" /> */}
<div className="cursor-default">
<Tooltip tooltipHeading="Owned by" tooltipContent={ownerDetails?.display_name}>
<Avatar src={ownerDetails?.avatar} name={ownerDetails?.display_name} />
</Tooltip>
</div>
<Circle className="h-1 w-1 fill-custom-text-300" />
{/* <span className="text-xs cursor-default">10m read</span>
<Circle className="h-1 w-1 fill-custom-text-300" /> */}
<div className="cursor-default">
<Tooltip tooltipContent={access === 0 ? "Public" : "Private"}>
{access === 0 ? <UsersRound className="h-3 w-3" /> : <Lock className="h-3 w-3" />}
</Tooltip>
</div>
</div>
{/* vertical divider */}
<Minus className="h-5 w-5 text-custom-text-400 rotate-90 -mx-3" strokeWidth={1} />
{/* page info */}
<Tooltip tooltipContent={`Created on ${renderFormattedDate(created_at)}`}>
<span className="h-4 w-4 grid place-items-center cursor-default">
<Info className="h-4 w-4 text-custom-text-300" />
</span>
</Tooltip>
{/* favorite/unfavorite */}
<FavoriteStar
onClick={(e) => {
e.preventDefault();
e.stopPropagation();
handleFavorites();
}}
selected={is_favorite}
/>
{/* quick actions dropdown */}
<PageQuickActions pageId={pageId} projectId={projectId} workspaceSlug={workspaceSlug} />
</div>
</Link>
<ListItem
title={name ?? ""}
itemLink={`/${workspaceSlug}/projects/${projectId}/pages/${pageId}`}
actionableItems={<BlockItemAction workspaceSlug={workspaceSlug} projectId={projectId} pageId={pageId} />}
isMobile={isMobile}
/>
);
});

View file

@ -1,4 +1,3 @@
export * from "./created-at";
export * from "./created-by";
export * from "./labels";
export * from "./root";

View file

@ -1,93 +0,0 @@
import React, { useMemo, useState } from "react";
import sortBy from "lodash/sortBy";
import { observer } from "mobx-react";
// types
import { IIssueLabel } from "@plane/types";
// ui
import { Loader } from "@plane/ui";
// components
import { FilterHeader, FilterOption } from "@/components/issues";
const LabelIcons = ({ color }: { color: string }) => (
<span className="h-2.5 w-2.5 rounded-full" style={{ backgroundColor: color }} />
);
type Props = {
appliedFilters: string[] | null;
handleUpdate: (val: string) => void;
labels: IIssueLabel[] | undefined;
searchQuery: string;
};
export const FilterLabels: React.FC<Props> = observer((props) => {
const { appliedFilters, handleUpdate, labels, searchQuery } = props;
const [itemsToRender, setItemsToRender] = useState(5);
const [previewEnabled, setPreviewEnabled] = useState(true);
const appliedFiltersCount = appliedFilters?.length ?? 0;
const sortedOptions = useMemo(() => {
const filteredOptions = (labels || []).filter((label) =>
label.name.toLowerCase().includes(searchQuery.toLowerCase())
);
return sortBy(filteredOptions, [
(label) => !(appliedFilters ?? []).includes(label.id),
(label) => label.name.toLowerCase(),
]);
}, [appliedFilters, labels, searchQuery]);
const handleViewToggle = () => {
if (!sortedOptions) return;
if (itemsToRender === sortedOptions.length) setItemsToRender(5);
else setItemsToRender(sortedOptions.length);
};
return (
<>
<FilterHeader
title={`Labels${appliedFiltersCount > 0 ? ` (${appliedFiltersCount})` : ""}`}
isPreviewEnabled={previewEnabled}
handleIsPreviewEnabled={() => setPreviewEnabled(!previewEnabled)}
/>
{previewEnabled && (
<div>
{sortedOptions ? (
sortedOptions.length > 0 ? (
<>
{sortedOptions.slice(0, itemsToRender).map((label) => (
<FilterOption
key={label?.id}
isChecked={appliedFilters?.includes(label?.id) ? true : false}
onClick={() => handleUpdate(label?.id)}
icon={<LabelIcons color={label.color} />}
title={label.name}
/>
))}
{sortedOptions.length > 5 && (
<button
type="button"
className="ml-8 text-xs font-medium text-custom-primary-100"
onClick={handleViewToggle}
>
{itemsToRender === sortedOptions.length ? "View less" : "View all"}
</button>
)}
</>
) : (
<p className="text-xs italic text-custom-text-400">No matches found</p>
)
) : (
<Loader className="space-y-2">
<Loader.Item height="20px" />
<Loader.Item height="20px" />
<Loader.Item height="20px" />
</Loader>
)}
</div>
)}
</>
);
});

View file

@ -1,20 +1,19 @@
import { useState } from "react";
import { observer } from "mobx-react-lite";
import { Search, X } from "lucide-react";
import { IIssueLabel, TPageFilterProps, TPageFilters } from "@plane/types";
import { TPageFilterProps, TPageFilters } from "@plane/types";
// components
import { FilterOption } from "@/components/issues";
import { FilterCreatedBy, FilterCreatedDate, FilterLabels } from "@/components/pages";
import { FilterCreatedBy, FilterCreatedDate } from "@/components/pages";
type Props = {
filters: TPageFilters;
handleFiltersUpdate: <T extends keyof TPageFilters>(filterKey: T, filterValue: TPageFilters[T]) => void;
labels?: IIssueLabel[] | undefined;
memberIds?: string[] | undefined;
};
export const PageFiltersSelection: React.FC<Props> = observer((props) => {
const { filters, handleFiltersUpdate, labels, memberIds } = props;
const { filters, handleFiltersUpdate, memberIds } = props;
// states
const [filtersSearchQuery, setFiltersSearchQuery] = useState("");
@ -93,16 +92,6 @@ export const PageFiltersSelection: React.FC<Props> = observer((props) => {
memberIds={memberIds}
/>
</div>
{/* labels */}
<div className="py-2">
<FilterLabels
appliedFilters={filters.filters?.labels ?? null}
handleUpdate={(val) => handleFilters("labels", val)}
searchQuery={filtersSearchQuery}
labels={labels}
/>
</div>
</div>
</div>
);

View file

@ -1,6 +1,7 @@
export * from "./applied-filters";
export * from "./filters";
export * from "./block";
export * from "./block-item-action";
export * from "./order-by";
export * from "./root";
export * from "./search-input";

View file

@ -2,6 +2,8 @@ import { FC } from "react";
import { observer } from "mobx-react";
// types
import { TPageNavigationTabs } from "@plane/types";
// components
import { ListLayout } from "@/components/core/list";
// hooks
import { useProjectPages } from "@/hooks/store";
// components
@ -22,10 +24,10 @@ export const PagesListRoot: FC<TPagesListRoot> = observer((props) => {
if (!filteredPageIds) return <></>;
return (
<div className="relative w-full h-full overflow-hidden overflow-y-auto divide-y-[0.5px] divide-custom-border-200">
<ListLayout>
{filteredPageIds.map((pageId) => (
<PageListBlock key={pageId} workspaceSlug={workspaceSlug} projectId={projectId} pageId={pageId} />
))}
</div>
</ListLayout>
);
});