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

View File

@ -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>

View File

@ -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 (
<>

View File

@ -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>

View File

@ -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 (
<>

View File

@ -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>

View File

@ -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>

View File

@ -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>
);
}

View File

@ -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">

View File

@ -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>
</>

View File

@ -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 />

View File

@ -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>

View File

@ -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>

View File

@ -92,13 +92,13 @@ export const DashboardWidgets = observer(function DashboardWidgets() {
{!isWikiApp && <NoProjectsEmptyState />}
{isAnyWidgetEnabled ? (
<div className="flex flex-col">
<div className="flex flex-col gap-2">
{orderedWidgets.map((key) => {
const WidgetComponent = HOME_WIDGETS_LIST[key]?.component;
const isEnabled = widgetsMap[key]?.is_enabled;
if (!WidgetComponent || !isEnabled) return null;
return (
<div key={key} className="py-4">
<div key={key} className="py-3">
<WidgetComponent workspaceSlug={workspaceSlug.toString()} />
</div>
);

View File

@ -58,8 +58,8 @@ export const WorkspaceHomeView = observer(function WorkspaceHomeView() {
)}
<>
<HomePeekOverviewsRoot />
<ContentWrapper className="mx-auto scrollbar-hide gap-6 bg-surface-1 px-page-x">
<div className="mx-auto w-full max-w-[800px]">
<ContentWrapper className="mx-auto scrollbar-hide gap-6 bg-transparent px-page-x">
<div className="nodedc-workspace-page-shell mx-auto w-full max-w-[980px]">
{currentUser && <UserGreetingsView user={currentUser} />}
<DashboardWidgets />
</div>

View File

@ -16,13 +16,13 @@ export function AddLink(props: TProps) {
return (
<button
className="btn btn-primary flex h-[56px] w-[230px] gap-4 rounded-md border-[0.5px] border-subtle bg-surface-1 px-4"
className="nodedc-workspace-list-row flex h-[4.75rem] w-[230px] items-center gap-4 px-4"
onClick={onClick}
>
<div className="my-auto h-8 w-8 rounded-sm bg-layer-1/40 p-2">
<div className="grid size-9 place-items-center rounded-full bg-white/6">
<PlusIcon className="h-4 w-4 stroke-2 text-tertiary" />
</div>
<div className="my-auto text-13 font-medium">{t("home.quick_links.add")}</div>
<div className="text-13 font-medium text-primary">{t("home.quick_links.add")}</div>
</button>
);
}

View File

@ -40,7 +40,7 @@ export const ProjectLinkList = observer(function ProjectLinkList(props: TProject
maxHeight={150}
containerClassName="box-border min-h-[30px] flex flex-col"
fallback={<></>}
buttonClassName="bg-surface-2/20"
buttonClassName="nodedc-toolbar-pill justify-center"
>
<div className="mb-2 flex flex-1 flex-wrap gap-2">
{links.map((linkId) => (

View File

@ -9,6 +9,7 @@ import { observer } from "mobx-react";
import useSWR from "swr";
import { useTranslation } from "@plane/i18n";
import { Button } from "@plane/propel/button";
import { PlusIcon } from "@plane/propel/icons";
import type { THomeWidgetProps } from "@plane/types";
import { useHome } from "@/hooks/store/use-home";
@ -49,12 +50,15 @@ export const DashboardQuickLinks = observer(function DashboardQuickLinks(props:
<div className="mb-2">
<div className="mb-4 flex items-center justify-between">
<div className="text-14 font-semibold text-tertiary">{t("home.quick_links.title_plural")}</div>
<button
<Button
variant="secondary"
size="lg"
className="nodedc-toolbar-pill"
onClick={handleCreateLinkModal}
className="my-auto flex gap-1 text-13 font-medium text-accent-primary"
prependIcon={<PlusIcon className="size-4" />}
>
<PlusIcon className="my-auto size-4" /> <span>{t("home.quick_links.add")}</span>
</button>
<span>{t("home.quick_links.add")}</span>
</Button>
</div>
<div className="flex w-full flex-wrap">
{/* rendering links */}

View File

@ -93,7 +93,7 @@ export const RecentActivityWidget = observer(function RecentActivityWidget(props
maxHeight={415}
containerClassName="box-border min-h-[250px]"
fallback={<></>}
buttonClassName="bg-surface-2/20"
buttonClassName="nodedc-toolbar-pill justify-center"
>
<div className="mb-2 flex items-center justify-between">
<div className="text-14 font-semibold text-tertiary">{t("home.recents.title")}</div>

View File

@ -13,6 +13,7 @@ import { CopyIcon, EditIcon, TrashIcon } from "@plane/propel/icons";
import { Tooltip } from "@plane/propel/tooltip";
import type { TWorkspaceDraftIssue } from "@plane/types";
import { EIssuesStoreType } from "@plane/types";
import { useTranslation } from "@plane/i18n";
import type { TContextMenuItem } from "@plane/ui";
import { Row } from "@plane/ui";
import { cn } from "@plane/utils";
@ -46,6 +47,7 @@ export const DraftIssueBlock = observer(function DraftIssueBlock(props: Props) {
const { getIssueById, updateIssue, deleteIssue } = useWorkspaceDraftIssues();
const { sidebarCollapsed: isSidebarCollapsed } = useAppTheme();
const { getProjectIdentifierById } = useProject();
const { t } = useTranslation();
// ref
const issueRef = useRef<HTMLDivElement | null>(null);
// derived values
@ -65,7 +67,7 @@ export const DraftIssueBlock = observer(function DraftIssueBlock(props: Props) {
const MENU_ITEMS: TContextMenuItem[] = [
{
key: "edit",
title: "edit",
title: t("edit"),
icon: EditIcon,
action: () => {
setIssueToEdit(issue);
@ -74,7 +76,7 @@ export const DraftIssueBlock = observer(function DraftIssueBlock(props: Props) {
},
{
key: "make-a-copy",
title: "make_a_copy",
title: t("make_a_copy"),
icon: CopyIcon,
action: () => {
setCreateUpdateIssueModal(true);
@ -82,7 +84,7 @@ export const DraftIssueBlock = observer(function DraftIssueBlock(props: Props) {
},
{
key: "move-to-issues",
title: "move_to_project",
title: t("move_to_project"),
icon: SquareStackIcon,
action: () => {
setMoveToIssue(true);
@ -92,7 +94,7 @@ export const DraftIssueBlock = observer(function DraftIssueBlock(props: Props) {
},
{
key: "delete",
title: "delete",
title: t("delete"),
icon: TrashIcon,
action: () => {
setDeleteIssueModal(true);

View File

@ -9,6 +9,7 @@ import { observer } from "mobx-react";
import { useParams } from "next/navigation";
// icons
import { DueDatePropertyIcon, StartDatePropertyIcon } from "@plane/propel/icons";
import { useTranslation } from "@plane/i18n";
// types
import type { TIssuePriorities, TWorkspaceDraftIssue } from "@plane/types";
import { getDate, renderFormattedPayloadDate, shouldHighlightIssueDueDate } from "@plane/utils";
@ -49,6 +50,7 @@ export const DraftIssueProperties = observer(function DraftIssueProperties(props
const { getStateById } = useProjectState();
const { isMobile } = usePlatformOS();
const projectDetails = getProjectById(issue.project_id);
const { t } = useTranslation();
// router
const { workspaceSlug } = useParams();
@ -180,7 +182,7 @@ export const DraftIssueProperties = observer(function DraftIssueProperties(props
value={issue.start_date ?? null}
onChange={handleStartDate}
maxDate={maxDate}
placeholder="Start date"
placeholder={t("start_date")}
icon={<StartDatePropertyIcon className="h-3 w-3 flex-shrink-0" />}
buttonVariant={issue.start_date ? "border-with-text" : "border-without-text"}
optionsClassName="z-10"
@ -195,7 +197,7 @@ export const DraftIssueProperties = observer(function DraftIssueProperties(props
value={issue?.target_date ?? null}
onChange={handleTargetDate}
minDate={minDate}
placeholder="Due date"
placeholder={t("due_date")}
icon={<DueDatePropertyIcon className="h-3 w-3 flex-shrink-0" />}
buttonVariant={issue.target_date ? "border-with-text" : "border-without-text"}
buttonClassName={
@ -218,7 +220,7 @@ export const DraftIssueProperties = observer(function DraftIssueProperties(props
buttonVariant={issue.assignee_ids?.length > 0 ? "transparent-without-text" : "border-without-text"}
buttonClassName={issue.assignee_ids?.length > 0 ? "hover:bg-transparent px-0" : ""}
showTooltip={issue?.assignee_ids?.length === 0}
placeholder="Assignees"
placeholder={t("assignees")}
optionsClassName="z-10"
tooltipContent=""
renderByDefault={isMobile}

View File

@ -101,12 +101,12 @@ export const WorkspaceDraftIssuesRoot = observer(function WorkspaceDraftIssuesRo
) : (
<div
className={cn("h-11 text-13 font-medium transition-all", {
"nodedc-workspace-toolbar flex cursor-pointer items-center justify-center rounded-[1.25rem] border-0 bg-transparent px-4 py-3 text-primary underline-offset-2 hover:text-primary hover:underline":
"nodedc-toolbar-pill mx-auto flex cursor-pointer items-center justify-center px-5 text-primary":
paginationInfo?.next_page_results,
})}
onClick={handleNextIssues}
>
Load More &darr;
{t("common.load_more")}
</div>
)}
</Fragment>

View File

@ -9,6 +9,7 @@ import Link from "next/link";
import { useParams } from "next/navigation";
import { History, MessageSquare } from "lucide-react";
// plane imports
import { useTranslation } from "@plane/i18n";
import type { IUserActivityResponse } from "@plane/types";
import { calculateTimeAgo, getFileURL } from "@plane/utils";
// components
@ -30,6 +31,7 @@ export const ActivityList = observer(function ActivityList(props: Props) {
// store hooks
const { data: currentUser } = useUser();
const { getWorkspaceBySlug } = useWorkspace();
const { t } = useTranslation();
// derived values
const workspaceId = getWorkspaceBySlug(workspaceSlug?.toString() ?? "")?.id ?? "";
@ -72,7 +74,7 @@ export const ActivityList = observer(function ActivityList(props: Props) {
: activityItem.actor_detail.display_name}
</div>
<p className="mt-0.5 text-11 text-secondary">
Commented {calculateTimeAgo(activityItem.created_at)}
{t("commented")} {calculateTimeAgo(activityItem.created_at)}
</p>
</div>
<div className="issue-comments-section p-0">
@ -102,7 +104,7 @@ export const ActivityList = observer(function ActivityList(props: Props) {
) &&
!activityItem.field ? (
<span>
created <IssueLink activity={activityItem} />
{t("created").toLowerCase()} <IssueLink activity={activityItem} />
</span>
) : (
<ActivityMessage activity={activityItem} showIssue />
@ -155,7 +157,7 @@ export const ActivityList = observer(function ActivityList(props: Props) {
>
<span className="text-gray font-medium">
{currentUser?.id === activityItem.actor_detail.id
? "You"
? t("you")
: activityItem.actor_detail.display_name}
</span>
</Link>

View File

@ -10,6 +10,7 @@ import Link from "next/link";
import useSWR from "swr";
// icons
import { History, MessageSquare } from "lucide-react";
import { useTranslation } from "@plane/i18n";
import { calculateTimeAgo, getFileURL } from "@plane/utils";
// hooks
import { ActivityIcon, ActivityMessage } from "@/components/core/activity";
@ -35,6 +36,7 @@ export const ProfileActivityListPage = observer(function ProfileActivityListPage
const { cursor, perPage, updateResultsCount, updateTotalPages, updateEmptyState } = props;
// store hooks
const { data: currentUser } = useUser();
const { t } = useTranslation();
const { data: userProfileActivity } = useSWR(
USER_ACTIVITY({
@ -96,7 +98,7 @@ export const ProfileActivityListPage = observer(function ProfileActivityListPage
: activityItem.actor_detail.display_name}
</div>
<p className="mt-0.5 text-11 text-secondary">
Commented {calculateTimeAgo(activityItem.created_at)}
{t("commented")} {calculateTimeAgo(activityItem.created_at)}
</p>
</div>
<div className="issue-comments-section p-0">
@ -166,7 +168,7 @@ export const ProfileActivityListPage = observer(function ProfileActivityListPage
>
<span className="text-gray font-medium">
{currentUser?.id === activityItem.actor_detail.id
? "You"
? t("you")
: activityItem.actor_detail.display_name}
</span>
</Link>

View File

@ -44,7 +44,7 @@ export const ProfileActivity = observer(function ProfileActivity() {
return (
<div className="space-y-2">
<h3 className="text-16 font-medium">{t("profile.stats.recent_activity.title")}</h3>
<Card>
<Card className="nodedc-workspace-list-row border-0 bg-transparent p-5">
{userProfileActivity ? (
userProfileActivity.results.length > 0 ? (
<div className="space-y-5">
@ -60,14 +60,14 @@ export const ProfileActivity = observer(function ProfileActivity() {
<p className="inline text-13 text-secondary">
<span className="font-medium text-primary">
{currentUser?.id === activity.actor_detail?.id
? "You"
? t("you")
: activity.actor_detail?.display_name}{" "}
</span>
{activity.field ? (
<ActivityMessage activity={activity} showIssue />
) : (
<span>
created <IssueLink activity={activity} />
{t("created").toLowerCase()} <IssueLink activity={activity} />
</span>
)}
</p>

View File

@ -30,7 +30,7 @@ export function ProfilePriorityDistribution({ userProfile }: Props) {
<div className="flex flex-col space-y-2">
<h3 className="text-16 font-medium">{t("profile.stats.priority_distribution.title")}</h3>
{userProfile ? (
<Card>
<Card className="nodedc-workspace-list-row border-0 bg-transparent p-5">
{userProfile.priority_distribution.length > 0 ? (
<BarChart
className="h-[300px] w-full"

View File

@ -21,11 +21,16 @@ type Props = {
export function ProfileStateDistribution({ stateDistribution, userProfile }: Props) {
const { t } = useTranslation();
if (!userProfile) return null;
const stateGroupLabels = {
unstarted: t("yet_to_start"),
started: t("in_progress"),
completed: t("completed"),
} as const;
return (
<div className="flex flex-col space-y-2">
<h3 className="text-16 font-medium">{t("profile.stats.state_distribution.title")}</h3>
<Card className="h-full">
<Card className="nodedc-workspace-list-row h-full border-0 bg-transparent p-5">
{userProfile.state_distribution.length > 0 ? (
<div className="grid h-[300px] w-full grid-cols-1 gap-x-6 md:grid-cols-2">
<PieChart
@ -42,7 +47,9 @@ export function ProfileStateDistribution({ stateDistribution, userProfile }: Pro
id: group.state_group,
key: group.state_group,
value: group.state_count,
name: capitalizeFirstLetter(group.state_group),
name:
stateGroupLabels[group.state_group as keyof typeof stateGroupLabels] ??
capitalizeFirstLetter(group.state_group),
color: STATE_GROUPS[group.state_group]?.color,
})) ?? []
}
@ -69,7 +76,10 @@ export function ProfileStateDistribution({ stateDistribution, userProfile }: Pro
STATE_GROUPS[group.state_group]?.color ?? "var(--background-color-accent-primary)",
}}
/>
<div className="whitespace-nowrap">{STATE_GROUPS[group.state_group].label}</div>
<div className="whitespace-nowrap">
{stateGroupLabels[group.state_group as keyof typeof stateGroupLabels] ??
STATE_GROUPS[group.state_group].label}
</div>
</div>
<div>{group.state_count}</div>
</div>

View File

@ -11,7 +11,7 @@ import { useParams } from "next/navigation";
import { useTranslation } from "@plane/i18n";
import { UserCirclePropertyIcon, CreateIcon, LayerStackIcon } from "@plane/propel/icons";
import type { IUserProfileData } from "@plane/types";
import { Loader, Card, ECardSpacing, ECardDirection } from "@plane/ui";
import { Loader } from "@plane/ui";
// types
type Props = {
@ -51,15 +51,15 @@ export function ProfileStats({ userProfile }: Props) {
<div className="grid grid-cols-1 gap-4 md:grid-cols-2 lg:grid-cols-3">
{overviewCards.map((card) => (
<Link key={card.route} href={`/${workspaceSlug}/profile/${userId}/${card.route}`}>
<Card direction={ECardDirection.ROW} spacing={ECardSpacing.SM} className="h-full">
<div className="grid h-11 w-11 place-items-center rounded-sm bg-surface-2">
<div className="nodedc-workspace-stat-card flex h-full items-center gap-4 p-5">
<div className="grid h-11 w-11 place-items-center rounded-full bg-white/6">
<card.icon className="h-5 w-5" />
</div>
<div className="space-y-1">
<p className="text-13 text-placeholder">{t(card.i18n_title)}</p>
<p className="text-18 font-semibold">{card.value}</p>
</div>
</Card>
</div>
</Link>
))}
</div>

View File

@ -18,6 +18,11 @@ type Props = {
export function ProfileWorkload({ stateDistribution }: Props) {
const { t } = useTranslation();
const stateGroupLabels = {
unstarted: t("yet_to_start"),
started: t("in_progress"),
completed: t("completed"),
} as const;
return (
<div className="space-y-2">
@ -26,7 +31,11 @@ export function ProfileWorkload({ stateDistribution }: Props) {
{stateDistribution.map((group) => (
<div key={group.state_group}>
<a>
<Card direction={ECardDirection.ROW} spacing={ECardSpacing.SM}>
<Card
direction={ECardDirection.ROW}
spacing={ECardSpacing.SM}
className="nodedc-workspace-stat-card border-0 bg-transparent"
>
<div
className="my-2 h-3 w-3 rounded-xs"
style={{
@ -35,11 +44,8 @@ export function ProfileWorkload({ stateDistribution }: Props) {
/>
<div className="flex-col space-y-1">
<span className="text-13 text-placeholder">
{group.state_group === "unstarted"
? "Not started"
: group.state_group === "started"
? "Working on"
: STATE_GROUPS[group.state_group].label}
{stateGroupLabels[group.state_group as keyof typeof stateGroupLabels] ??
STATE_GROUPS[group.state_group].label}
</span>
<p className="text-18 font-semibold">{group.state_count}</p>
</div>

View File

@ -87,7 +87,7 @@ export const ProfileSidebar = observer(function ProfileSidebar(props: TProfileSi
return (
<div
className={cn(
`vertical-scrollbar fixed z-5 scrollbar-md h-full w-full shrink-0 overflow-hidden overflow-y-auto border-l border-subtle bg-surface-1 shadow-raised-200 transition-all md:relative md:w-[300px]`,
`vertical-scrollbar fixed z-5 scrollbar-md h-full w-full shrink-0 overflow-hidden overflow-y-auto border-l border-white/6 bg-[rgba(10,10,14,0.92)] shadow-[0_28px_60px_rgba(0,0,0,0.32)] backdrop-blur-2xl transition-all md:relative md:w-[320px]`,
className
)}
style={profileSidebarCollapsed ? { marginLeft: `${window?.innerWidth || 0}px` } : {}}
@ -99,6 +99,7 @@ export const ProfileSidebar = observer(function ProfileSidebar(props: TProfileSi
<div className="absolute top-3.5 right-3.5">
<IconButton
variant="secondary"
className="nodedc-toolbar-icon-button rounded-full"
icon={EditIcon}
onClick={() =>
toggleProfileSettingsModal({
@ -112,18 +113,18 @@ export const ProfileSidebar = observer(function ProfileSidebar(props: TProfileSi
<CoverImage
src={userData?.cover_image_url ?? undefined}
alt={userData?.display_name}
className="h-[110px] w-full"
className="h-[110px] w-full rounded-b-[1.35rem]"
showDefaultWhenEmpty
/>
<div className="absolute -bottom-[26px] left-5 h-[52px] w-[52px] rounded-sm">
<div className="absolute -bottom-[26px] left-5 h-[52px] w-[52px] rounded-[1rem]">
{userData?.avatar_url && userData?.avatar_url !== "" ? (
<img
src={getFileURL(userData?.avatar_url)}
alt={userData?.display_name}
className="h-full w-full rounded-sm object-cover"
className="h-full w-full rounded-[1rem] object-cover"
/>
) : (
<div className="flex h-[52px] w-[52px] items-center justify-center rounded-sm bg-accent-primary text-on-color capitalize">
<div className="flex h-[52px] w-[52px] items-center justify-center rounded-[1rem] bg-accent-primary text-on-color capitalize">
{userData?.first_name?.[0]}
</div>
)}
@ -171,7 +172,11 @@ export const ProfileSidebar = observer(function ProfileSidebar(props: TProfileSi
</div>
<div className="flex flex-shrink-0 items-center gap-2">
{project.assigned_issues > 0 && (
<Tooltip tooltipContent="Completion percentage" position="left" isMobile={isMobile}>
<Tooltip
tooltipContent={`${t("completed")}: ${completedIssuePercentage}%`}
position="left"
isMobile={isMobile}
>
<div
className={`rounded-sm px-1 py-0.5 text-11 font-medium ${
completedIssuePercentage <= 35
@ -234,7 +239,7 @@ export const ProfileSidebar = observer(function ProfileSidebar(props: TProfileSi
<div className="flex items-center justify-between gap-2">
<div className="flex items-center gap-2">
<div className="h-2.5 w-2.5 rounded-xs bg-[#203b80]" />
Created
{t("created")}
</div>
<div className="font-medium">
{project.created_issues} {t("issues")}
@ -243,7 +248,7 @@ export const ProfileSidebar = observer(function ProfileSidebar(props: TProfileSi
<div className="flex items-center justify-between gap-2">
<div className="flex items-center gap-2">
<div className="h-2.5 w-2.5 rounded-xs bg-[#3f76ff]" />
Assigned
{t("assigned")}
</div>
<div className="font-medium">
{project.assigned_issues} {t("issues")}
@ -252,7 +257,7 @@ export const ProfileSidebar = observer(function ProfileSidebar(props: TProfileSi
<div className="flex items-center justify-between gap-2">
<div className="flex items-center gap-2">
<div className="h-2.5 w-2.5 rounded-xs bg-[#f59e0b]" />
Due
{t("due_date")}
</div>
<div className="font-medium">
{project.pending_issues} {t("issues")}
@ -261,7 +266,7 @@ export const ProfileSidebar = observer(function ProfileSidebar(props: TProfileSi
<div className="flex items-center justify-between gap-2">
<div className="flex items-center gap-2">
<div className="h-2.5 w-2.5 rounded-xs bg-[#16a34a]" />
Completed
{t("completed")}
</div>
<div className="font-medium">
{project.completed_issues} {t("issues")}

View File

@ -30,10 +30,10 @@ export function SettingsContentWrapper(props: Props) {
<div
className={cn("py-9", {
"w-full px-page-x lg:px-12": hugging,
"mx-auto w-full max-w-225 px-page-x @min-[58.95rem]:px-0": !hugging, // 58.95rem = max-width(56.25rem) + padding-x(1.35rem * 2)
"mx-auto w-full max-w-[74rem] px-page-x @min-[58.95rem]:px-6": !hugging,
})}
>
{children}
<div className="nodedc-workspace-page-shell space-y-7">{children}</div>
</div>
</ScrollArea>
</div>

View File

@ -14,10 +14,10 @@ export function SettingsControlItem(props: Props) {
const { control, description, title } = props;
return (
<div className="flex w-full flex-col items-start gap-4 py-3 md:flex-row md:items-center md:justify-between md:gap-8">
<div className="flex flex-col gap-1">
<div className="nodedc-settings-card flex w-full flex-col items-start gap-4 px-5 py-4 md:flex-row md:items-center md:justify-between md:gap-8">
<div className="flex flex-col gap-1.5">
<h4 className="text-body-sm-medium text-primary">{title}</h4>
<p className="text-caption-md-regular text-secondary">{description}</p>
<p className="max-w-2xl text-caption-md-regular text-tertiary">{description}</p>
</div>
<div className="shrink-0">{control}</div>
</div>

View File

@ -17,8 +17,8 @@ type Props = {
export function SettingsHeading({ className, control, description, title, variant = "h3" }: Props) {
return (
<div className={cn("flex flex-col items-start justify-between gap-4 md:flex-row md:items-center", className)}>
<div className="flex flex-col items-start gap-1">
<div className={cn("flex flex-col items-start justify-between gap-4 md:flex-row md:items-end", className)}>
<div className="flex flex-col items-start gap-1.5">
{title && (
<h3
className={cn("text-h3-medium text-primary", {
@ -30,9 +30,9 @@ export function SettingsHeading({ className, control, description, title, varian
{title}
</h3>
)}
{description && <p className="text-body-xs-regular text-tertiary">{description}</p>}
{description && <p className="max-w-2xl text-body-xs-regular text-tertiary">{description}</p>}
</div>
{control}
{control && <div className="shrink-0">{control}</div>}
</div>
);
}

View File

@ -31,17 +31,22 @@ export const SettingsMobileNav = observer(function SettingsMobileNav(props: Prop
});
return (
<div className="flex items-center gap-4 border-b border-subtle px-page-x py-3 md:hidden">
<div className="flex items-center gap-3 border-b border-white/6 px-page-x py-3 md:hidden">
<div ref={sidebarRef} className="relative z-50 w-fit">
{!sidebarCollapsed && (
<div className="absolute top-10.5 left-0 z-50">
<HamburgerContent className="max-h-100 rounded-lg border border-subtle pb-3" />
<HamburgerContent className="max-h-100 rounded-[1.35rem] pb-3 shadow-2xl" />
</div>
)}
<IconButton variant="secondary" className="group z-50 shrink-0" icon={Menu} onClick={() => toggleSidebar()} />
<IconButton
variant="secondary"
className="nodedc-toolbar-icon-button group z-50 shrink-0"
icon={Menu}
onClick={() => toggleSidebar()}
/>
</div>
{/* path */}
<div className="flex items-center gap-2">
<div className="nodedc-toolbar-pill flex items-center gap-2 px-3.5">
<ChevronRightIcon className="size-4 text-tertiary" />
<span className="text-13 font-medium text-secondary">{t(activePath)}</span>
</div>

View File

@ -24,15 +24,17 @@ export const ProfileSettingsContent = observer(function ProfileSettingsContent(p
return (
<ScrollArea
className={cn("shrink-0 overflow-y-scroll bg-surface-1", className)}
className={cn("shrink-0 overflow-y-scroll bg-transparent", className)}
viewportClassName="px-8 py-9"
scrollType="hover"
orientation="vertical"
size="sm"
>
<Suspense>
<PageComponent />
</Suspense>
<div className="nodedc-workspace-page-shell space-y-7">
<Suspense>
<PageComponent />
</Suspense>
</div>
</ScrollArea>
);
});

View File

@ -16,21 +16,21 @@ export const ProfileSettingsSidebarHeader = observer(function ProfileSettingsSid
const { data: currentUser } = useUser();
return (
<div className="flex shrink-0 items-center gap-2">
<div className="nodedc-settings-card flex shrink-0 items-center gap-3 px-4 py-3">
<div className="shrink-0">
<Avatar
src={getFileURL(currentUser?.avatar_url ?? "")}
name={currentUser?.display_name}
size={32}
size={40}
shape="circle"
className="text-16"
/>
</div>
<div className="truncate">
<p className="truncate text-body-sm-medium">
<p className="truncate text-body-sm-medium text-primary">
{currentUser?.first_name} {currentUser?.last_name}
</p>
<p className="truncate text-caption-md-regular">{currentUser?.email}</p>
<p className="truncate text-caption-md-medium text-tertiary">{currentUser?.email}</p>
</div>
</div>
);

View File

@ -55,7 +55,7 @@ export const ProfileSettingsSidebarItemCategories = observer(function ProfileSet
return (
<div key={category} className="shrink-0">
<div className="p-2 text-caption-md-medium text-tertiary capitalize">
<div className="px-3 py-1.5 text-[11px] font-semibold uppercase tracking-[0.18em] text-tertiary">
{t(PROFILE_CATEGORY_I18N_KEYS[category])}
</div>
<div className="flex flex-col">

View File

@ -26,7 +26,7 @@ export function ProfileSettingsSidebarRoot(props: Props) {
scrollType="hover"
orientation="vertical"
size="sm"
rootClassName={cn("shrink-0 overflow-y-scroll border-r border-r-subtle bg-surface-2 px-3 py-4", className)}
rootClassName={cn("nodedc-settings-sidebar-shell shrink-0 overflow-y-scroll px-3 py-4", className)}
>
<ProfileSettingsSidebarHeader />
<ProfileSettingsSidebarItemCategories activeTab={activeTab} updateActiveTab={updateActiveTab} />

View File

@ -41,22 +41,23 @@ export const ProjectSettingsSidebarHeader = observer(function ProjectSettingsSid
return (
<div className="shrink-0">
<div className="flex items-center gap-1 py-3 pr-5 pl-4 text-body-md-medium">
<div className="flex items-center gap-2 px-3 pb-3 text-body-md-medium">
<IconButton
variant="ghost"
variant="secondary"
size="base"
icon={ArrowLeft}
className="nodedc-toolbar-icon-button"
onClick={() => router.push(`/${currentWorkspace?.slug}/projects/${projectId}/issues/`)}
/>
<p>{t("project_settings_label")}</p>
</div>
<div className="mt-1.5 flex items-center gap-2 truncate px-5 py-0.5">
<div className="grid size-8 shrink-0 place-items-center rounded bg-layer-2">
<div className="nodedc-settings-card flex items-center gap-3 px-4 py-3">
<div className="grid size-10 shrink-0 place-items-center rounded-[1rem] bg-white/6">
<Logo logo={projectDetails?.logo_props} size={20} />
</div>
<div className="truncate">
<p className="truncate text-body-sm-medium">{projectDetails?.name}</p>
<p className="truncate text-caption-md-regular">{t(ROLE_DETAILS[currentProjectRole].i18n_title)}</p>
<p className="truncate text-body-sm-medium text-primary">{projectDetails?.name}</p>
<p className="truncate text-caption-md-medium text-tertiary">{t(ROLE_DETAILS[currentProjectRole].i18n_title)}</p>
</div>
</div>
</div>

View File

@ -41,7 +41,7 @@ export const ProjectSettingsSidebarItemCategories = observer(function ProjectSet
const { t } = useTranslation();
return (
<div className="mt-3 flex flex-col divide-y divide-subtle px-3">
<div className="mt-4 flex flex-col divide-y divide-white/6">
{PROJECT_SETTINGS_CATEGORIES.map((category) => {
const categoryItems = GROUPED_PROJECT_SETTINGS[category];
const accessibleItems = categoryItems.filter((item) =>
@ -51,8 +51,8 @@ export const ProjectSettingsSidebarItemCategories = observer(function ProjectSet
if (accessibleItems.length === 0) return null;
return (
<div key={category} className="shrink-0 py-3 first:pt-0 last:pb-0">
<div className="p-2 text-caption-md-medium text-tertiary capitalize">
<div key={category} className="shrink-0 py-3.5 first:pt-0 last:pb-0">
<div className="px-3 py-1.5 text-[11px] font-semibold uppercase tracking-[0.18em] text-tertiary">
{t(PROJECT_CATEGORY_I18N_KEYS[category])}
</div>
<div className="flex flex-col">

View File

@ -22,7 +22,7 @@ export function ProjectSettingsSidebarRoot(props: Props) {
scrollType="hover"
orientation="vertical"
size="sm"
rootClassName="shrink-0 animate-fade-in h-full w-[250px] bg-surface-1 border-r border-r-subtle overflow-y-scroll"
rootClassName="nodedc-settings-sidebar-shell h-full w-[296px] shrink-0 animate-fade-in overflow-y-scroll px-3 py-4"
viewportClassName="pb-5"
>
<ProjectSettingsSidebarHeader projectId={projectId} />

View File

@ -26,10 +26,9 @@ export function SettingsSidebarItem(props: Props) {
const { as, isActive, label } = props;
// common class
const className = cn(
"flex items-center gap-2 rounded-lg px-2 py-1.5 text-left text-body-sm-medium text-secondary transition-colors",
"nodedc-settings-sidebar-item flex items-center gap-3 text-left text-body-sm-medium transition-colors",
{
"bg-layer-transparent-selected text-primary": isActive,
"hover:bg-layer-transparent-hover": !isActive,
"text-primary": isActive,
}
);
// common content
@ -46,14 +45,14 @@ export function SettingsSidebarItem(props: Props) {
if (as === "button") {
return (
<button type="button" className={className} onClick={props.onClick}>
<button type="button" className={className} onClick={props.onClick} data-active={isActive ? "true" : "false"}>
{content}
</button>
);
}
return (
<Link className={className} href={props.href}>
<Link className={className} href={props.href} data-active={isActive ? "true" : "false"}>
{content}
</Link>
);

View File

@ -35,26 +35,27 @@ export const WorkspaceSettingsSidebarHeader = observer(function WorkspaceSetting
if (!currentWorkspaceRole) return null;
return (
<div className="shrink-0">
<div className="flex items-center gap-1 py-3 pr-5 pl-4 text-body-md-medium">
<div className="shrink-0">
<div className="flex items-center gap-2 px-3 pb-3 text-body-md-medium">
<IconButton
variant="ghost"
variant="secondary"
size="base"
icon={ArrowLeft}
className="nodedc-toolbar-icon-button"
onClick={() => router.push(`/${currentWorkspace?.slug}/`)}
/>
<p>{t("workspace_settings.label")}</p>
</div>
<div className="mt-1.5 flex items-center justify-between gap-2 px-5 py-0.5">
<div className="flex items-center gap-2 truncate">
<div className="nodedc-settings-card flex items-center justify-between gap-3 px-4 py-3">
<div className="flex items-center gap-3 truncate">
<WorkspaceLogo
logo={currentWorkspace?.logo_url}
name={currentWorkspace?.name}
classNames="shrink-0 size-8 border border-subtle"
classNames="shrink-0 size-10 rounded-[1rem] border border-white/8"
/>
<div className="truncate">
<p className="truncate text-body-sm-medium">{currentWorkspace?.name}</p>
<p className="truncate text-caption-md-regular">{t(ROLE_DETAILS[currentWorkspaceRole].i18n_title)}</p>
<p className="truncate text-body-sm-medium text-primary">{currentWorkspace?.name}</p>
<p className="truncate text-caption-md-medium text-tertiary">{t(ROLE_DETAILS[currentWorkspaceRole].i18n_title)}</p>
</div>
</div>
<div className="shrink-0">

View File

@ -30,7 +30,7 @@ export const WorkspaceSettingsSidebarItemCategories = observer(function Workspac
const { t } = useTranslation();
return (
<div className="mt-3 flex flex-col divide-y divide-subtle px-3">
<div className="mt-4 flex flex-col divide-y divide-white/6">
{WORKSPACE_SETTINGS_CATEGORIES.map((category) => {
const categoryItems = GROUPED_WORKSPACE_SETTINGS[category];
const accessibleItems = categoryItems.filter(
@ -42,8 +42,10 @@ export const WorkspaceSettingsSidebarItemCategories = observer(function Workspac
if (accessibleItems.length === 0) return null;
return (
<div key={category} className="shrink-0 py-3 first:pt-0 last:pb-0">
<div className="p-2 text-caption-md-medium text-tertiary capitalize">{t(category)}</div>
<div key={category} className="shrink-0 py-3.5 first:pt-0 last:pb-0">
<div className="px-3 py-1.5 text-[11px] font-semibold uppercase tracking-[0.18em] text-tertiary">
{t(category)}
</div>
<div className="flex flex-col">
{accessibleItems.map((item) => {
const isItemActive =

View File

@ -24,7 +24,7 @@ export function WorkspaceSettingsSidebarRoot(props: Props) {
orientation="vertical"
size="sm"
rootClassName={cn(
"h-full w-[250px] shrink-0 animate-fade-in overflow-y-scroll border-r border-r-subtle bg-surface-1",
"nodedc-settings-sidebar-shell h-full w-[296px] shrink-0 animate-fade-in overflow-y-scroll px-3 py-4",
className
)}
>

View File

@ -11,6 +11,7 @@ import useSWR from "swr";
import { StickyNote as StickyIcon } from "lucide-react";
// plane hooks
import { useOutsideClickDetector } from "@plane/hooks";
import { useTranslation } from "@plane/i18n";
// plane ui
import { RecentStickyIcon, StickyNoteIcon, PlusIcon, CloseIcon } from "@plane/propel/icons";
import { Tooltip } from "@plane/propel/tooltip";
@ -31,6 +32,7 @@ export const StickyActionBar = observer(function StickyActionBar() {
const [showRecentSticky, setShowRecentSticky] = useState(false);
// navigation
const { workspaceSlug } = useParams();
const { t } = useTranslation();
// refs
const ref = useRef(null);
// store hooks
@ -61,9 +63,9 @@ export const StickyActionBar = observer(function StickyActionBar() {
<div
className={`flex origin-bottom flex-col gap-2 transition-all duration-300 ease-in-out ${isExpanded ? "mb-2 scale-y-100 opacity-100 " : "h-0 scale-y-0 opacity-0"}`}
>
<Tooltip tooltipContent="All stickies" isMobile={false} position="left">
<Tooltip tooltipContent={t("stickies.all")} isMobile={false} position="left">
<button
className="btn btn--icon shadow-sm flex h-10 w-10 items-center justify-center rounded-full bg-surface-1"
className="nodedc-toolbar-icon-button grid h-10 w-10 place-items-center rounded-full"
onClick={() => toggleAllStickiesModal(true)}
>
<RecentStickyIcon className="size-5 rotate-90 text-tertiary" />
@ -92,7 +94,7 @@ export const StickyActionBar = observer(function StickyActionBar() {
disabled={showRecentSticky}
>
<button
className="btn btn--icon shadow-sm flex h-10 w-10 items-center justify-center rounded-full bg-surface-1"
className="nodedc-toolbar-icon-button grid h-10 w-10 place-items-center rounded-full"
onClick={() => setShowRecentSticky(true)}
style={{ color: recentStickyBackgroundColor }}
>
@ -100,9 +102,9 @@ export const StickyActionBar = observer(function StickyActionBar() {
</button>
</Tooltip>
)}
<Tooltip tooltipContent="Add sticky" isMobile={false} position="left">
<Tooltip tooltipContent={t("stickies.add")} isMobile={false} position="left">
<button
className="btn btn--icon shadow-sm flex h-10 w-10 items-center justify-center rounded-full bg-surface-1"
className="nodedc-toolbar-icon-button grid h-10 w-10 place-items-center rounded-full"
onClick={() => {
updateActiveStickyId("");
toggleShowNewSticky(true);
@ -115,7 +117,7 @@ export const StickyActionBar = observer(function StickyActionBar() {
</div>
<button
className={`btn btn--icon shadow-sm flex h-10 w-10 items-center justify-center rounded-full bg-surface-1 transition-transform duration-300 ${isExpanded ? "rotate-180" : ""}`}
className={`nodedc-toolbar-icon-button grid h-10 w-10 place-items-center rounded-full transition-transform duration-300 ${isExpanded ? "rotate-180" : ""}`}
onClick={() => setIsExpanded(!isExpanded)}
>
{isExpanded ? (

View File

@ -43,9 +43,7 @@ export const StickiesTruncated = observer(function StickiesTruncated(props: Stic
customButton={
<Link
href={`/${workspaceSlug}/stickies`}
className={cn(
"w-full gap-1 bg-surface-2/20 text-13 font-medium text-accent-primary transition-opacity duration-300"
)}
className={cn("nodedc-toolbar-pill flex w-full items-center justify-center gap-2 text-13 font-medium")}
onClick={handleClose}
>
{t("show_all")}

View File

@ -58,7 +58,7 @@ export const StickySearch = observer(function StickySearch() {
<IconButton
variant="ghost"
size="lg"
className="-mr-2"
className="nodedc-toolbar-icon-button"
icon={SearchIcon}
onClick={() => {
setIsSearchOpen(true);
@ -68,9 +68,9 @@ export const StickySearch = observer(function StickySearch() {
)}
<div
className={cn(
"ml-auto flex w-0 items-center justify-start gap-1 overflow-hidden rounded-md border border-transparent text-placeholder opacity-0 transition-[width] ease-linear",
"nodedc-toolbar-pill ml-auto flex w-0 items-center justify-start gap-2 overflow-hidden px-0 text-placeholder opacity-0 transition-[width] ease-linear",
{
"w-30 border-subtle px-2.5 py-1.5 opacity-100 md:w-64": isSearchOpen,
"w-30 px-3 opacity-100 md:w-64": isSearchOpen,
}
)}
>
@ -89,7 +89,7 @@ export const StickySearch = observer(function StickySearch() {
{isSearchOpen && (
<button
type="button"
className="grid place-items-center"
className="grid h-7 w-7 flex-shrink-0 place-items-center rounded-full text-secondary transition-colors hover:bg-white/6 hover:text-primary"
onClick={() => {
updateSearchQuery("");
setIsSearchOpen(false);

View File

@ -8,6 +8,8 @@ import { observer } from "mobx-react";
import { useParams } from "next/navigation";
// plane ui
import { useTranslation } from "@plane/i18n";
import { Button } from "@plane/propel/button";
import { RecentStickyIcon, PlusIcon, CloseIcon } from "@plane/propel/icons";
// hooks
import { useSticky } from "@/hooks/use-stickies";
@ -24,6 +26,7 @@ export const Stickies = observer(function Stickies(props: TProps) {
const { handleClose } = props;
// navigation
const { workspaceSlug } = useParams();
const { t } = useTranslation();
// store hooks
const { creatingSticky, toggleShowNewSticky } = useSticky();
// sticky operations
@ -36,20 +39,23 @@ export const Stickies = observer(function Stickies(props: TProps) {
{/* Title */}
<div className="flex items-center gap-2 text-secondary">
<RecentStickyIcon className="size-5 flex-shrink-0 rotate-90" />
<p className="text-18 font-medium">Your stickies</p>
<p className="text-18 font-medium">{t("stickies.title")}</p>
</div>
{/* actions */}
<div className="flex gap-2">
<StickySearch />
<button
<Button
variant="primary"
size="lg"
className="nodedc-toolbar-primary"
onClick={() => {
toggleShowNewSticky(true);
stickyOperations.create();
}}
className="my-auto flex gap-1 text-13 font-medium text-accent-primary"
disabled={creatingSticky}
prependIcon={<PlusIcon className="size-4" />}
>
<PlusIcon className="my-auto size-4" /> <span>Add sticky</span>
<span>{t("stickies.add")}</span>
{creatingSticky && (
<div className="ml-2 flex items-center justify-center">
<div
@ -59,12 +65,12 @@ export const Stickies = observer(function Stickies(props: TProps) {
/>
</div>
)}
</button>
</Button>
{handleClose && (
<button
type="button"
onClick={handleClose}
className="my-auto grid flex-shrink-0 place-items-center rounded-sm p-1 text-tertiary transition-colors hover:bg-layer-1 hover:text-primary"
className="nodedc-toolbar-icon-button my-auto grid h-10 w-10 flex-shrink-0 place-items-center rounded-full"
>
<CloseIcon className="size-4 text-placeholder" />
</button>

View File

@ -80,14 +80,17 @@ export const StickyNote = observer(function StickyNote(props: TProps) {
handleClose={() => setIsDeleteModalOpen(false)}
/>
<div
className={cn("group/sticky flex h-fit w-full flex-col overflow-y-scroll rounded-sm", className)}
className={cn(
"group/sticky flex h-fit w-full flex-col overflow-y-scroll rounded-[1.35rem] shadow-[0_16px_36px_rgba(0,0,0,0.18)]",
className
)}
style={{
backgroundColor,
}}
>
{/* {isStickiesPage && <StickyItemDragHandle isDragging={false} />}{" "} */}
{onClose && (
<button type="button" className="flex flex-shrink-0 justify-end p-2.5" onClick={onClose}>
<button type="button" className="flex flex-shrink-0 justify-end p-3" onClick={onClose}>
<Minimize2 className="size-4" />
</button>
)}

View File

@ -9,6 +9,7 @@ import { useParams } from "next/navigation";
// plane imports
import { useTranslation } from "@plane/i18n";
import { Button } from "@plane/propel/button";
import { PlusIcon } from "@plane/propel/icons";
// hooks
import { useSticky } from "@/hooks/use-stickies";
@ -35,15 +36,17 @@ export const StickiesWidget = observer(function StickiesWidget() {
{/* actions */}
<div className="flex gap-2">
<StickySearch />
<button
<Button
variant="primary"
size="lg"
className="nodedc-toolbar-primary"
onClick={() => {
toggleShowNewSticky(true);
stickyOperations.create();
}}
className="my-auto flex gap-1 text-13 font-medium text-accent-primary"
disabled={creatingSticky}
prependIcon={<PlusIcon className="size-4" />}
>
<PlusIcon className="my-auto size-4" />
<span>{t("stickies.add")}</span>
{creatingSticky && (
<div
@ -52,7 +55,7 @@ export const StickiesWidget = observer(function StickiesWidget() {
aria-label="loading"
/>
)}
</button>
</Button>
</div>
</div>
<div className="-mx-2">

View File

@ -717,6 +717,49 @@
-webkit-backdrop-filter: blur(18px);
}
.nodedc-settings-sidebar-shell {
border: 0 !important;
outline: none !important;
box-shadow:
inset -1px 0 0 rgba(255, 255, 255, 0.06),
inset 0 1px 0 rgba(255, 255, 255, 0.015) !important;
background:
linear-gradient(180deg, rgba(255, 255, 255, 0.024) 0%, rgba(255, 255, 255, 0.008) 100%),
rgba(8, 8, 11, 0.9) !important;
-webkit-backdrop-filter: blur(28px);
backdrop-filter: blur(28px);
}
.nodedc-settings-sidebar-item {
min-height: 2.75rem;
border: 0 !important;
outline: none !important;
box-shadow: none !important;
border-radius: 1.1rem !important;
background: transparent !important;
color: rgba(255, 255, 255, 0.76) !important;
padding-inline: 0.95rem !important;
}
.nodedc-settings-sidebar-item:hover {
background:
linear-gradient(180deg, rgba(255, 255, 255, 0.03) 0%, rgba(255, 255, 255, 0.014) 100%),
rgba(255, 255, 255, 0.028) !important;
color: var(--text-color-primary) !important;
}
.nodedc-settings-sidebar-item[data-active="true"] {
background:
linear-gradient(180deg, rgba(255, 255, 255, 0.04) 0%, rgba(255, 255, 255, 0.016) 100%),
rgba(255, 255, 255, 0.042) !important;
color: rgb(var(--nodedc-accent-rgb)) !important;
box-shadow: inset 0 0 0 1px rgba(var(--nodedc-accent-rgb), 0.24) !important;
}
.nodedc-settings-sidebar-item[data-active="true"] * {
color: rgb(var(--nodedc-accent-rgb)) !important;
}
.nodedc-settings-field {
background:
linear-gradient(180deg, rgba(255, 255, 255, 0.028) 0%, rgba(255, 255, 255, 0.012) 100%),

View File

@ -477,6 +477,7 @@ export default {
assignee: "Assignee",
assignees: "Assignees",
you: "You",
commented: "Commented",
labels: "Labels",
create_new_label: "Create new label",
start_date: "Start date",

View File

@ -633,6 +633,7 @@ export default {
assignee: "Назначенный",
assignees: "Назначенные",
you: "Вы",
commented: "Прокомментировал",
labels: "Метки",
create_new_label: "Создать новую метку",
start_date: "Дата начала",