UI - МЕЖПРОЕКТНАЯ КОММУНИКАЦИЯ: редизайн drafts stickies profile home и settings-shell

This commit is contained in:
DCCONSTRUCTIONS
2026-04-22 16:49:20 +03:00
parent aa9c278b59
commit 290b00d251
53 changed files with 287 additions and 180 deletions
@@ -8,14 +8,13 @@ import { useState } from "react";
import { observer } from "mobx-react";
import { EUserPermissions, EUserPermissionsLevel } from "@plane/constants";
import { useTranslation } from "@plane/i18n";
// ui
import { Button } from "@plane/propel/button";
import { DraftIcon } from "@plane/propel/icons";
import { EIssuesStoreType } from "@plane/types";
import { Breadcrumbs, Header } from "@plane/ui";
// components
import { BreadcrumbLink } from "@/components/common/breadcrumb-link";
import { CountChip } from "@/components/common/count-chip";
import { AppHeaderPrimaryActionButton } from "@/components/core/app-header/primary-action-button";
import { CreateUpdateIssueModal } from "@/components/issues/issue-modal/modal";
// hooks
@@ -66,15 +65,12 @@ export const WorkspaceDraftHeader = observer(function WorkspaceDraftHeader() {
<Header.RightItem>
{joinedProjectIds && joinedProjectIds.length > 0 && (
<Button
variant="primary"
size="lg"
className="items-center gap-1"
<AppHeaderPrimaryActionButton
onClick={() => setIsDraftIssueModalOpen(true)}
disabled={!isAuthorizedUser}
>
{t("workspace_draft_issues.draft_an_issue")}
</Button>
</AppHeaderPrimaryActionButton>
)}
</Header.RightItem>
</Header>
@@ -5,13 +5,15 @@
*/
// components
import { useTranslation } from "@plane/i18n";
import { PageHead } from "@/components/core/page-title";
import { WorkspaceDraftIssuesRoot } from "@/components/issues/workspace-draft";
import type { Route } from "./+types/page";
function WorkspaceDraftPage({ params }: Route.ComponentProps) {
const { workspaceSlug } = params;
const pageTitle = "Workspace Draft";
const { t } = useTranslation();
const pageTitle = t("sidebar.drafts");
return (
<>
@@ -41,7 +41,7 @@ export const WorkspaceDashboardHeader = observer(function WorkspaceDashboardHead
variant="secondary"
size="lg"
onClick={() => toggleWidgetSettings(true)}
className="my-auto mb-0"
className="nodedc-toolbar-pill my-auto mb-0"
prependIcon={<Shapes />}
>
<div className="hidden sm:hidden md:block">{t("home.manage_widgets")}</div>
@@ -5,15 +5,16 @@
*/
import React from "react";
import { useTranslation } from "@plane/i18n";
// components
import { PageHead } from "@/components/core/page-title";
import { ProfileIssuesPage } from "@/components/profile/profile-issues";
import type { Route } from "./+types/page";
const ProfilePageHeader = {
assigned: "Profile - Assigned",
created: "Profile - Created",
subscribed: "Profile - Subscribed",
assigned: "profile.tabs.assigned",
created: "profile.tabs.created",
subscribed: "profile.tabs.subscribed",
};
function isValidProfileViewId(viewId: string): viewId is keyof typeof ProfilePageHeader {
@@ -22,10 +23,11 @@ function isValidProfileViewId(viewId: string): viewId is keyof typeof ProfilePag
function ProfileIssuesTypePage({ params }: Route.ComponentProps) {
const { profileViewId } = params;
const { t } = useTranslation();
if (!isValidProfileViewId(profileViewId)) return null;
const header = ProfilePageHeader[profileViewId];
const header = `${t("profile.page_label")} - ${t(ProfilePageHeader[profileViewId])}`;
return (
<>
@@ -51,20 +51,21 @@ function ProfileActivityPage() {
[EUserPermissions.ADMIN, EUserPermissions.MEMBER],
EUserPermissionsLevel.WORKSPACE
);
const pageTitle = `${t("profile.page_label")} - ${t("profile.tabs.activity")}`;
return (
<>
<PageHead title="Profile - Activity" />
<div className="flex h-full w-full flex-col overflow-hidden py-5">
<div className="flex items-center justify-between gap-2 px-5 md:px-9">
<PageHead title={pageTitle} />
<div className="nodedc-workspace-page-shell flex h-full w-full flex-col gap-5 overflow-hidden">
<div className="flex items-center justify-between gap-2">
<h3 className="text-16 font-medium">{t("profile.stats.recent_activity.title")}</h3>
{canDownloadActivity && <DownloadActivityButton />}
</div>
<div className="vertical-scrollbar flex scrollbar-md h-full flex-col overflow-y-auto px-5 md:px-9">
<div className="vertical-scrollbar flex scrollbar-md h-full flex-col overflow-y-auto">
{activityPages}
{pageCount < totalPages && resultsCount !== 0 && (
<div className="flex w-full items-center justify-center text-11">
<Button variant="secondary" onClick={handleLoadMore}>
<Button variant="secondary" size="lg" className="nodedc-toolbar-pill" onClick={handleLoadMore}>
{t("common.load_more")}
</Button>
</div>
@@ -20,7 +20,6 @@ import { ProfileIssuesFilter } from "@/components/profile/profile-issues-filter"
// hooks
import { useAppTheme } from "@/hooks/store/use-app-theme";
import { useUser, useUserPermissions } from "@/hooks/store/user";
import { Button } from "@plane/propel/button";
type TUserProfileHeader = {
userProjectsData: IUserProfileProjectSegregation | undefined;
@@ -75,8 +74,10 @@ export const UserProfileHeader = observer(function UserProfileHeader(props: TUse
<SelectionDropdown
placement="bottom-start"
menuButton={
<div className="flex items-center gap-2 rounded-md border border-subtle px-2 py-1.5">
<span className="flex flex-grow justify-center text-13 text-secondary">{type}</span>
<div className="nodedc-toolbar-pill flex items-center gap-2 px-3.5">
<span className="flex flex-grow justify-center text-13 text-secondary">
{type ? t(type) : t("profile.tabs.summary")}
</span>
<ChevronDownIcon className="h-4 w-4 text-placeholder" />
</div>
}
@@ -89,16 +90,15 @@ export const UserProfileHeader = observer(function UserProfileHeader(props: TUse
}))}
/>
<div className="shrink-0 md:hidden">
<Button
variant="ghost"
size="lg"
<button
type="button"
className="nodedc-toolbar-icon-button grid h-10 w-10 place-items-center rounded-full"
onClick={() => {
toggleProfileSidebar();
}}
appendIcon={
<PanelRight className={!profileSidebarCollapsed ? "text-accent-primary" : "text-secondary"} />
}
></Button>
>
<PanelRight className={!profileSidebarCollapsed ? "text-accent-primary" : "text-secondary"} />
</button>
</div>
</div>
</Header.RightItem>
@@ -9,7 +9,6 @@ import { useParams, usePathname } from "next/navigation";
// plane imports
import { PROFILE_VIEWER_TAB, PROFILE_ADMINS_TAB } from "@plane/constants";
import { useTranslation } from "@plane/i18n";
import { Header, EHeaderVariant } from "@plane/ui";
import { cn } from "@plane/utils";
type Props = {
@@ -25,17 +24,21 @@ export function ProfileNavbar(props: Props) {
const tabsList = isAuthorized ? [...PROFILE_VIEWER_TAB, ...PROFILE_ADMINS_TAB] : PROFILE_VIEWER_TAB;
return (
<Header variant={EHeaderVariant.SECONDARY} showOnMobile={false}>
<div className="flex items-center overflow-x-scroll">
<div className="hidden border-b border-white/6 px-5 py-3 md:block md:px-9">
<div className="flex items-center gap-2 overflow-x-auto">
{tabsList.map((tab) => (
<Link key={tab.route} href={`/${workspaceSlug}/profile/${userId}/${tab.route}`}>
<Link
key={tab.route}
href={`/${workspaceSlug}/profile/${userId}/${tab.route}`}
className={cn(
"nodedc-toolbar-pill whitespace-nowrap px-4 text-13 font-medium",
pathname === `/${workspaceSlug}/profile/${userId}${tab.selected}` ? "text-primary" : "text-secondary"
)}
data-active={pathname === `/${workspaceSlug}/profile/${userId}${tab.selected}` ? "true" : "false"}
>
<span
className={cn(
`flex border-b-2 p-4 text-13 font-medium whitespace-nowrap text-tertiary outline-none hover:text-primary ${
pathname === `/${workspaceSlug}/profile/${userId}${tab.selected}`
? "border-accent-strong text-accent-primary hover:text-accent-primary"
: "border-transparent"
}`
"flex items-center justify-center"
)}
>
{t(tab.i18n_label)}
@@ -43,6 +46,6 @@ export function ProfileNavbar(props: Props) {
</Link>
))}
</div>
</Header>
</div>
);
}
@@ -42,7 +42,7 @@ export default function ProfileOverviewPage({ params }: Route.ComponentProps) {
return (
<>
<PageHead title={t("profile.page_label")} />
<ContentWrapper className="space-y-7">
<ContentWrapper className="nodedc-workspace-page-shell space-y-7">
<ProfileStats userProfile={userProfile} />
<ProfileWorkload stateDistribution={stateDistribution} />
<div className="grid grid-cols-1 items-stretch gap-5 xl:grid-cols-2">
@@ -7,11 +7,12 @@
import { observer } from "mobx-react";
import { useParams } from "next/navigation";
// plane imports
import { Button } from "@plane/propel/button";
import { useTranslation } from "@plane/i18n";
import { RecentStickyIcon } from "@plane/propel/icons";
import { Breadcrumbs, Header } from "@plane/ui";
// components
import { BreadcrumbLink } from "@/components/common/breadcrumb-link";
import { AppHeaderPrimaryActionButton } from "@/components/core/app-header/primary-action-button";
import { StickySearch } from "@/components/stickies/modal/search";
import { useStickyOperations } from "@/components/stickies/sticky/use-operations";
// hooks
@@ -19,6 +20,7 @@ import { useSticky } from "@/hooks/use-stickies";
export const WorkspaceStickyHeader = observer(function WorkspaceStickyHeader() {
const { workspaceSlug } = useParams();
const { t } = useTranslation();
// hooks
const { creatingSticky, toggleShowNewSticky } = useSticky();
const { stickyOperations } = useStickyOperations({ workspaceSlug: workspaceSlug?.toString() });
@@ -32,7 +34,7 @@ export const WorkspaceStickyHeader = observer(function WorkspaceStickyHeader() {
<Breadcrumbs.Item
component={
<BreadcrumbLink
label={`Stickies`}
label={t("sidebar.stickies")}
icon={<RecentStickyIcon className="size-5 rotate-90 text-secondary" />}
/>
}
@@ -43,17 +45,15 @@ export const WorkspaceStickyHeader = observer(function WorkspaceStickyHeader() {
<Header.RightItem>
<StickySearch />
<Button
variant="primary"
size="lg"
<AppHeaderPrimaryActionButton
onClick={() => {
toggleShowNewSticky(true);
stickyOperations.create();
}}
loading={creatingSticky}
>
Add sticky
</Button>
{t("stickies.add")}
</AppHeaderPrimaryActionButton>
</Header.RightItem>
</Header>
</>
@@ -5,13 +5,16 @@
*/
// components
import { useTranslation } from "@plane/i18n";
import { PageHead } from "@/components/core/page-title";
import { StickiesInfinite } from "@/components/stickies/layout/stickies-infinite";
export default function WorkspaceStickiesPage() {
const { t } = useTranslation();
return (
<>
<PageHead title="Your stickies" />
<PageHead title={t("sidebar.stickies")} />
<div className="relative h-full w-full overflow-hidden overflow-y-auto">
<div className="nodedc-workspace-page-shell h-full">
<StickiesInfinite />
@@ -13,10 +13,10 @@ export default function SettingsLayout() {
return (
<>
<ProjectsAppPowerKProvider />
<div className="relative flex size-full overflow-hidden rounded-lg border border-subtle">
<main className="relative flex size-full flex-col overflow-hidden">
<div className="relative flex size-full overflow-hidden bg-canvas p-2">
<main className="relative flex size-full flex-col overflow-hidden rounded-[1.6rem] border border-white/6 bg-[rgba(10,10,14,0.9)] shadow-[0_20px_48px_rgba(0,0,0,0.24)] backdrop-blur-2xl">
{/* Content */}
<ContentWrapper className="w-full bg-surface-1 md:flex">
<ContentWrapper className="w-full bg-transparent md:flex">
<div className="size-full overflow-hidden">
<Outlet />
</div>
@@ -16,7 +16,7 @@ export default function ProfileSettingsLayout() {
<ProjectsAppPowerKProvider />
<AuthenticationWrapper>
<div className="relative flex size-full overflow-hidden bg-canvas p-2">
<main className="relative flex size-full flex-col overflow-hidden rounded-lg border border-subtle bg-surface-1">
<main className="relative flex size-full flex-col overflow-hidden rounded-[1.6rem] border border-white/6 bg-[rgba(10,10,14,0.9)] shadow-[0_20px_48px_rgba(0,0,0,0.24)] backdrop-blur-2xl">
<div className="size-full overflow-hidden">
<Outlet />
</div>