[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:
parent
1b79517f07
commit
87a606446f
34 changed files with 1230 additions and 1081 deletions
|
|
@ -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>
|
||||
|
|
|
|||
94
web/components/pages/list/block-item-action.tsx
Normal file
94
web/components/pages/list/block-item-action.tsx
Normal 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} />
|
||||
</>
|
||||
);
|
||||
});
|
||||
|
|
@ -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}
|
||||
/>
|
||||
);
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1,4 +1,3 @@
|
|||
export * from "./created-at";
|
||||
export * from "./created-by";
|
||||
export * from "./labels";
|
||||
export * from "./root";
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
});
|
||||
|
|
@ -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>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -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";
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
);
|
||||
});
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue