UI - МЕЖПРОЕКТНАЯ КОММУНИКАЦИЯ: выправка дизайна views аналитики и рабочих экранов
This commit is contained in:
parent
3f6219fc50
commit
aa9c278b59
|
|
@ -74,20 +74,20 @@ function AnalyticsPage({ params }: Route.ComponentProps) {
|
||||||
{workspaceProjectIds.length > 0 || loader === "init-loader" ? (
|
{workspaceProjectIds.length > 0 || loader === "init-loader" ? (
|
||||||
<div className="flex h-full overflow-hidden">
|
<div className="flex h-full overflow-hidden">
|
||||||
<Tabs value={selectedTab} onValueChange={handleTabChange} className="h-full w-full">
|
<Tabs value={selectedTab} onValueChange={handleTabChange} className="h-full w-full">
|
||||||
<div className={"flex h-full w-full flex-col"}>
|
<div className="nodedc-workspace-page-shell flex h-full w-full flex-col gap-4 overflow-hidden">
|
||||||
<div
|
<div
|
||||||
className={cn(
|
className={cn(
|
||||||
"flex w-full items-center justify-between gap-4 overflow-hidden border-b border-subtle bg-surface-1 px-6 py-2"
|
"nodedc-workspace-toolbar flex w-full flex-wrap items-center justify-between gap-3 overflow-hidden px-4 py-3 md:flex-nowrap md:px-5"
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
<Tabs.List className={"flex h-7 w-fit overflow-x-auto"}>
|
<Tabs.List className="flex h-9 w-fit max-w-full overflow-x-auto rounded-full bg-white/6 p-1">
|
||||||
{ANALYTICS_TABS.map((tab) => (
|
{ANALYTICS_TABS.map((tab) => (
|
||||||
<Tabs.Trigger
|
<Tabs.Trigger
|
||||||
key={tab.key}
|
key={tab.key}
|
||||||
value={tab.key}
|
value={tab.key}
|
||||||
disabled={tab.isDisabled}
|
disabled={tab.isDisabled}
|
||||||
size="md"
|
size="md"
|
||||||
className="h-6 px-3"
|
className="h-7 rounded-full px-3.5"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
if (!tab.isDisabled) {
|
if (!tab.isDisabled) {
|
||||||
handleTabChange(tab.key);
|
handleTabChange(tab.key);
|
||||||
|
|
@ -107,7 +107,7 @@ function AnalyticsPage({ params }: Route.ComponentProps) {
|
||||||
<Tabs.Content
|
<Tabs.Content
|
||||||
key={tab.key}
|
key={tab.key}
|
||||||
value={tab.key}
|
value={tab.key}
|
||||||
className={"h-full overflow-hidden overflow-y-auto px-2"}
|
className="h-full overflow-hidden overflow-y-auto px-0"
|
||||||
>
|
>
|
||||||
<tab.content />
|
<tab.content />
|
||||||
</Tabs.Content>
|
</Tabs.Content>
|
||||||
|
|
|
||||||
|
|
@ -17,8 +17,10 @@ function WorkspaceDraftPage({ params }: Route.ComponentProps) {
|
||||||
<>
|
<>
|
||||||
<PageHead title={pageTitle} />
|
<PageHead title={pageTitle} />
|
||||||
<div className="relative h-full w-full overflow-hidden overflow-y-auto">
|
<div className="relative h-full w-full overflow-hidden overflow-y-auto">
|
||||||
|
<div className="nodedc-workspace-page-shell h-full">
|
||||||
<WorkspaceDraftIssuesRoot workspaceSlug={workspaceSlug} />
|
<WorkspaceDraftIssuesRoot workspaceSlug={workspaceSlug} />
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -13,8 +13,10 @@ export default function WorkspaceStickiesPage() {
|
||||||
<>
|
<>
|
||||||
<PageHead title="Your stickies" />
|
<PageHead title="Your stickies" />
|
||||||
<div className="relative h-full w-full overflow-hidden overflow-y-auto">
|
<div className="relative h-full w-full overflow-hidden overflow-y-auto">
|
||||||
|
<div className="nodedc-workspace-page-shell h-full">
|
||||||
<StickiesInfinite />
|
<StickiesInfinite />
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -15,13 +15,13 @@ import {
|
||||||
DEFAULT_GLOBAL_VIEWS_LIST,
|
DEFAULT_GLOBAL_VIEWS_LIST,
|
||||||
} from "@plane/constants";
|
} from "@plane/constants";
|
||||||
import { useTranslation } from "@plane/i18n";
|
import { useTranslation } from "@plane/i18n";
|
||||||
import { Button } from "@plane/propel/button";
|
|
||||||
import { ViewsIcon } from "@plane/propel/icons";
|
import { ViewsIcon } from "@plane/propel/icons";
|
||||||
import type { IIssueDisplayFilterOptions, IIssueDisplayProperties, ICustomSearchSelectOption } from "@plane/types";
|
import type { IIssueDisplayFilterOptions, IIssueDisplayProperties, ICustomSearchSelectOption } from "@plane/types";
|
||||||
import { EIssuesStoreType, EIssueLayoutTypes } from "@plane/types";
|
import { EIssuesStoreType, EIssueLayoutTypes } from "@plane/types";
|
||||||
import { Breadcrumbs, Header, BreadcrumbNavigationSearchDropdown } from "@plane/ui";
|
import { Breadcrumbs, Header, BreadcrumbNavigationSearchDropdown } from "@plane/ui";
|
||||||
// components
|
// components
|
||||||
import { BreadcrumbLink } from "@/components/common/breadcrumb-link";
|
import { BreadcrumbLink } from "@/components/common/breadcrumb-link";
|
||||||
|
import { AppHeaderPrimaryActionButton } from "@/components/core/app-header/primary-action-button";
|
||||||
import { SwitcherLabel } from "@/components/common/switcher-label";
|
import { SwitcherLabel } from "@/components/common/switcher-label";
|
||||||
import { DisplayFiltersSelection, FiltersDropdown } from "@/components/issues/issue-layouts/filters";
|
import { DisplayFiltersSelection, FiltersDropdown } from "@/components/issues/issue-layouts/filters";
|
||||||
import { WorkItemFiltersToggle } from "@/components/work-item-filters/filters-toggle";
|
import { WorkItemFiltersToggle } from "@/components/work-item-filters/filters-toggle";
|
||||||
|
|
@ -170,14 +170,12 @@ export const GlobalIssuesHeader = observer(function GlobalIssuesHeader() {
|
||||||
/>
|
/>
|
||||||
</FiltersDropdown>
|
</FiltersDropdown>
|
||||||
)}
|
)}
|
||||||
<Button
|
<AppHeaderPrimaryActionButton
|
||||||
variant="primary"
|
|
||||||
size="lg"
|
|
||||||
data-ph-element={GLOBAL_VIEW_TRACKER_ELEMENTS.RIGHT_HEADER_ADD_BUTTON}
|
data-ph-element={GLOBAL_VIEW_TRACKER_ELEMENTS.RIGHT_HEADER_ADD_BUTTON}
|
||||||
onClick={() => setCreateViewModal(true)}
|
onClick={() => setCreateViewModal(true)}
|
||||||
>
|
>
|
||||||
{t("workspace_views.add_view")}
|
{t("workspace_views.add_view")}
|
||||||
</Button>
|
</AppHeaderPrimaryActionButton>
|
||||||
<div className="hidden md:block">
|
<div className="hidden md:block">
|
||||||
{viewDetails && <WorkspaceViewQuickActions workspaceSlug={workspaceSlug?.toString()} view={viewDetails} />}
|
{viewDetails && <WorkspaceViewQuickActions workspaceSlug={workspaceSlug?.toString()} view={viewDetails} />}
|
||||||
{isDefaultView && defaultViewDetails && (
|
{isDefaultView && defaultViewDetails && (
|
||||||
|
|
|
||||||
|
|
@ -30,17 +30,18 @@ function WorkspaceViewsPage() {
|
||||||
<>
|
<>
|
||||||
<PageHead title={pageTitle} />
|
<PageHead title={pageTitle} />
|
||||||
<div className="flex h-full w-full flex-col overflow-hidden">
|
<div className="flex h-full w-full flex-col overflow-hidden">
|
||||||
<div className="flex h-11 w-full items-center gap-2.5 overflow-hidden border-b border-subtle px-5 py-3">
|
<div className="nodedc-workspace-page-shell flex h-full w-full flex-col gap-4 overflow-hidden">
|
||||||
|
<div className="nodedc-workspace-toolbar flex min-h-14 w-full items-center gap-3 overflow-hidden px-4 py-3 md:px-5">
|
||||||
<SearchIcon className="text-secondary" width={14} height={14} strokeWidth={2} />
|
<SearchIcon className="text-secondary" width={14} height={14} strokeWidth={2} />
|
||||||
<Input
|
<Input
|
||||||
className="w-full bg-transparent !p-0 text-11 leading-5 text-secondary placeholder:text-placeholder focus:outline-none"
|
className="w-full bg-transparent !p-0 text-13 leading-5 text-primary placeholder:text-placeholder focus:outline-none"
|
||||||
value={query}
|
value={query}
|
||||||
onChange={(e) => setQuery(e.target.value)}
|
onChange={(e) => setQuery(e.target.value)}
|
||||||
placeholder="Search"
|
placeholder="Search workspace views"
|
||||||
mode="true-transparent"
|
mode="true-transparent"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className="vertical-scrollbar flex scrollbar-lg h-full w-full flex-col">
|
<div className="vertical-scrollbar flex scrollbar-lg h-full w-full flex-col gap-3 overflow-y-auto pr-1">
|
||||||
{DEFAULT_GLOBAL_VIEWS_LIST.filter((v) => t(v.i18n_label).toLowerCase().includes(query.toLowerCase())).map(
|
{DEFAULT_GLOBAL_VIEWS_LIST.filter((v) => t(v.i18n_label).toLowerCase().includes(query.toLowerCase())).map(
|
||||||
(option) => (
|
(option) => (
|
||||||
<GlobalDefaultViewListItem key={option.key} view={option} />
|
<GlobalDefaultViewListItem key={option.key} view={option} />
|
||||||
|
|
@ -49,6 +50,7 @@ function WorkspaceViewsPage() {
|
||||||
<GlobalViewsList searchQuery={query} />
|
<GlobalViewsList searchQuery={query} />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -16,14 +16,14 @@ type Props = {
|
||||||
};
|
};
|
||||||
|
|
||||||
function AnalyticsSectionWrapper(props: Props) {
|
function AnalyticsSectionWrapper(props: Props) {
|
||||||
const { title, children, className, actions, headerClassName } = props;
|
const { title, children, className, actions, headerClassName, subtitle } = props;
|
||||||
return (
|
return (
|
||||||
<div className={className}>
|
<div className={cn("nodedc-external-section rounded-[1.7rem] px-5 py-5 md:px-6 md:py-6", className)}>
|
||||||
<div className={cn("mb-6 flex items-center gap-2 text-nowrap", headerClassName)}>
|
<div className={cn("mb-5 flex flex-wrap items-start justify-between gap-3", headerClassName)}>
|
||||||
{title && (
|
{title && (
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex flex-col gap-1">
|
||||||
<h1 className={"text-16 font-medium"}>{title}</h1>
|
<h1 className="text-16 font-semibold text-primary">{title}</h1>
|
||||||
{/* {subtitle && <p className="text-16 text-tertiary"> • {subtitle}</p>} */}
|
{subtitle ? <p className="text-12 text-secondary">{subtitle}</p> : null}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
{actions}
|
{actions}
|
||||||
|
|
|
||||||
|
|
@ -19,8 +19,13 @@ function AnalyticsWrapper(props: Props) {
|
||||||
const { i18nTitle, children, className } = props;
|
const { i18nTitle, children, className } = props;
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
return (
|
return (
|
||||||
<div className={cn("px-6 py-4", className)}>
|
<div className={cn("flex flex-col gap-4 pb-5", className)}>
|
||||||
<h1 className={"mb-4 text-20 font-bold md:mb-6"}>{t(i18nTitle)}</h1>
|
<div className="nodedc-external-panel flex items-center justify-between gap-3 px-5 py-4 md:px-6">
|
||||||
|
<div className="flex flex-col gap-1">
|
||||||
|
<span className="text-11 font-medium tracking-[0.24em] text-secondary uppercase">Analytics</span>
|
||||||
|
<h1 className="text-20 font-semibold text-primary md:text-[1.45rem]">{t(i18nTitle)}</h1>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
{children}
|
{children}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -20,11 +20,11 @@ function InsightCard(props: InsightCardProps) {
|
||||||
const count = data?.count ?? 0;
|
const count = data?.count ?? 0;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex flex-col gap-3">
|
<div className="nodedc-workspace-stat-card flex min-h-[7.5rem] flex-col justify-between gap-3 px-4 py-4 md:px-5">
|
||||||
<div className="text-13 text-tertiary">{label}</div>
|
<div className="text-12 font-medium text-secondary">{label}</div>
|
||||||
{!isLoading ? (
|
{!isLoading ? (
|
||||||
<div className="flex flex-col gap-1">
|
<div className="flex flex-col gap-1">
|
||||||
<div className="text-20 font-bold text-primary">{count}</div>
|
<div className="text-[1.65rem] font-semibold text-primary">{count}</div>
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<Loader.Item height="50px" width="100%" />
|
<Loader.Item height="50px" width="100%" />
|
||||||
|
|
|
||||||
|
|
@ -23,9 +23,11 @@ type Props = {
|
||||||
|
|
||||||
function CompletionPercentage({ percentage }: { percentage: number }) {
|
function CompletionPercentage({ percentage }: { percentage: number }) {
|
||||||
const percentageColor =
|
const percentageColor =
|
||||||
percentage > 50 ? "bg-success-subtle text-success-primary" : "bg-danger-subtle text-danger-primary";
|
percentage > 50
|
||||||
|
? "bg-success-subtle/80 text-success-primary"
|
||||||
|
: "bg-danger-subtle/80 text-danger-primary";
|
||||||
return (
|
return (
|
||||||
<div className={cn("flex items-center gap-2 rounded-sm p-1 text-11", percentageColor)}>
|
<div className={cn("flex items-center gap-2 rounded-full px-2.5 py-1 text-11 font-medium", percentageColor)}>
|
||||||
<span>{percentage}%</span>
|
<span>{percentage}%</span>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
@ -41,9 +43,9 @@ function ActiveProjectItem(props: Props) {
|
||||||
if (!projectDetails) return null;
|
if (!projectDetails) return null;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex w-full items-center justify-between gap-2">
|
<div className="nodedc-workspace-list-row flex w-full items-center justify-between gap-3 px-3 py-3">
|
||||||
<div className="flex flex-1 items-center gap-2 overflow-hidden">
|
<div className="flex flex-1 items-center gap-2 overflow-hidden">
|
||||||
<div className="flex h-8 w-8 shrink-0 items-center justify-center rounded-xl bg-layer-1">
|
<div className="flex h-9 w-9 shrink-0 items-center justify-center rounded-[1rem] bg-white/6">
|
||||||
<span className="grid h-4 w-4 flex-shrink-0 place-items-center">
|
<span className="grid h-4 w-4 flex-shrink-0 place-items-center">
|
||||||
{projectDetails?.logo_props ? (
|
{projectDetails?.logo_props ? (
|
||||||
<Logo logo={projectDetails?.logo_props} size={16} />
|
<Logo logo={projectDetails?.logo_props} size={16} />
|
||||||
|
|
|
||||||
|
|
@ -63,7 +63,7 @@ const ProjectInsights = observer(function ProjectInsights() {
|
||||||
<EmptyStateCompact
|
<EmptyStateCompact
|
||||||
assetKey="unknown"
|
assetKey="unknown"
|
||||||
assetClassName="size-20"
|
assetClassName="size-20"
|
||||||
rootClassName="border border-subtle px-5 py-10 md:py-20 md:px-20"
|
rootClassName="nodedc-workspace-list-row px-5 py-10 md:px-20 md:py-20"
|
||||||
title={t("workspace_empty_state.analytics_work_items.title")}
|
title={t("workspace_empty_state.analytics_work_items.title")}
|
||||||
/>
|
/>
|
||||||
) : (
|
) : (
|
||||||
|
|
@ -95,9 +95,13 @@ const ProjectInsights = observer(function ProjectInsights() {
|
||||||
/>
|
/>
|
||||||
</Suspense>
|
</Suspense>
|
||||||
)}
|
)}
|
||||||
<div className="w-full lg:w-2/5">
|
<div className="nodedc-workspace-list-row flex w-full flex-col gap-3 px-4 py-4 lg:w-2/5">
|
||||||
<div className="text-13 text-tertiary">{t("workspace_analytics.summary_of_projects")}</div>
|
<div className="text-12 font-medium tracking-[0.16em] text-secondary uppercase">
|
||||||
<div className="mb-3 border-b border-subtle py-2">{t("workspace_analytics.all_projects")}</div>
|
{t("workspace_analytics.summary_of_projects")}
|
||||||
|
</div>
|
||||||
|
<div className="border-b border-white/8 pb-3 text-13 font-medium text-primary">
|
||||||
|
{t("workspace_analytics.all_projects")}
|
||||||
|
</div>
|
||||||
<div className="flex flex-col gap-4">
|
<div className="flex flex-col gap-4">
|
||||||
<div className="flex items-center justify-between text-13 text-tertiary">
|
<div className="flex items-center justify-between text-13 text-tertiary">
|
||||||
<div>{t("workspace_analytics.trend_on_charts")}</div>
|
<div>{t("workspace_analytics.trend_on_charts")}</div>
|
||||||
|
|
|
||||||
|
|
@ -13,9 +13,9 @@ import ProjectInsights from "./project-insights";
|
||||||
function Overview() {
|
function Overview() {
|
||||||
return (
|
return (
|
||||||
<AnalyticsWrapper i18nTitle="common.overview">
|
<AnalyticsWrapper i18nTitle="common.overview">
|
||||||
<div className="flex flex-col gap-14">
|
<div className="flex flex-col gap-5 md:gap-6">
|
||||||
<TotalInsights analyticsType="overview" />
|
<TotalInsights analyticsType="overview" />
|
||||||
<div className="grid grid-cols-1 gap-14 md:grid-cols-5">
|
<div className="grid grid-cols-1 gap-5 md:grid-cols-5 md:gap-6">
|
||||||
<ProjectInsights />
|
<ProjectInsights />
|
||||||
<ActiveProjects />
|
<ActiveProjects />
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,6 @@
|
||||||
|
|
||||||
import { observer } from "mobx-react";
|
import { observer } from "mobx-react";
|
||||||
// plane package imports
|
// plane package imports
|
||||||
import { getButtonStyling } from "@plane/propel/button";
|
|
||||||
import { Logo } from "@plane/propel/emoji-icon-picker";
|
import { Logo } from "@plane/propel/emoji-icon-picker";
|
||||||
import { ChevronDownIcon, ProjectIcon } from "@plane/propel/icons";
|
import { ChevronDownIcon, ProjectIcon } from "@plane/propel/icons";
|
||||||
import { SearchSelectionDropdown } from "@plane/ui";
|
import { SearchSelectionDropdown } from "@plane/ui";
|
||||||
|
|
@ -49,8 +48,14 @@ export const ProjectSelect = observer(function ProjectSelect(props: Props) {
|
||||||
onChange={(val: string[]) => onChange(val)}
|
onChange={(val: string[]) => onChange(val)}
|
||||||
options={options}
|
options={options}
|
||||||
className="border-none p-0"
|
className="border-none p-0"
|
||||||
menuButton={
|
menuButton={({ open }) => (
|
||||||
<div className={cn(getButtonStyling("secondary", "lg"), "gap-2")}>
|
<div
|
||||||
|
className={cn(
|
||||||
|
"nodedc-toolbar-pill nodedc-toolbar-pill-wide flex min-h-[2.5rem] items-center gap-2 px-4 py-0 text-13 font-medium",
|
||||||
|
open && "text-[rgb(var(--nodedc-accent-rgb))]"
|
||||||
|
)}
|
||||||
|
data-active={open}
|
||||||
|
>
|
||||||
<ProjectIcon className="h-4 w-4" />
|
<ProjectIcon className="h-4 w-4" />
|
||||||
{value && value.length > 3
|
{value && value.length > 3
|
||||||
? `3+ projects`
|
? `3+ projects`
|
||||||
|
|
@ -62,7 +67,7 @@ export const ProjectSelect = observer(function ProjectSelect(props: Props) {
|
||||||
: "All projects"}
|
: "All projects"}
|
||||||
<ChevronDownIcon className="h-3 w-3" aria-hidden="true" />
|
<ChevronDownIcon className="h-3 w-3" aria-hidden="true" />
|
||||||
</div>
|
</div>
|
||||||
}
|
)}
|
||||||
menuButtonWrapperClassName="h-auto w-auto border-none bg-transparent p-0 hover:bg-transparent"
|
menuButtonWrapperClassName="h-auto w-auto border-none bg-transparent p-0 hover:bg-transparent"
|
||||||
multiple
|
multiple
|
||||||
/>
|
/>
|
||||||
|
|
|
||||||
|
|
@ -14,7 +14,7 @@ import WorkItemsInsightTable from "./workitems-insight-table";
|
||||||
function WorkItems() {
|
function WorkItems() {
|
||||||
return (
|
return (
|
||||||
<AnalyticsWrapper i18nTitle="sidebar.work_items">
|
<AnalyticsWrapper i18nTitle="sidebar.work_items">
|
||||||
<div className="flex flex-col gap-14">
|
<div className="flex flex-col gap-5 md:gap-6">
|
||||||
<TotalInsights analyticsType="work-items" />
|
<TotalInsights analyticsType="work-items" />
|
||||||
<CreatedVsResolved />
|
<CreatedVsResolved />
|
||||||
<CustomizedInsights />
|
<CustomizedInsights />
|
||||||
|
|
|
||||||
|
|
@ -16,6 +16,7 @@ import { useTranslation } from "@plane/i18n";
|
||||||
// icon
|
// icon
|
||||||
import { CheckIcon, CycleGroupIcon, CycleIcon, SearchIcon } from "@plane/propel/icons";
|
import { CheckIcon, CycleGroupIcon, CycleIcon, SearchIcon } from "@plane/propel/icons";
|
||||||
import type { TCycleGroups } from "@plane/types";
|
import type { TCycleGroups } from "@plane/types";
|
||||||
|
import { cn } from "@plane/utils";
|
||||||
// ui
|
// ui
|
||||||
// store hooks
|
// store hooks
|
||||||
import { useCycle } from "@/hooks/store/use-cycle";
|
import { useCycle } from "@/hooks/store/use-cycle";
|
||||||
|
|
@ -126,17 +127,17 @@ export const CycleOptions = observer(function CycleOptions(props: CycleOptionsPr
|
||||||
return (
|
return (
|
||||||
<Combobox.Options className="fixed z-10" static>
|
<Combobox.Options className="fixed z-10" static>
|
||||||
<div
|
<div
|
||||||
className="my-1 w-48 rounded-sm border-[0.5px] border-strong bg-surface-1 px-2 py-2.5 text-11 shadow-raised-200 focus:outline-none"
|
className="nodedc-dropdown-surface z-30 my-1 w-52"
|
||||||
ref={setPopperElement}
|
ref={setPopperElement}
|
||||||
style={styles.popper}
|
style={styles.popper}
|
||||||
{...attributes.popper}
|
{...attributes.popper}
|
||||||
>
|
>
|
||||||
<div className="flex items-center gap-1.5 rounded-sm border border-subtle bg-surface-2 px-2">
|
<div className="nodedc-dropdown-search">
|
||||||
<SearchIcon className="h-3.5 w-3.5 text-placeholder" strokeWidth={1.5} />
|
<SearchIcon className="h-3.5 w-3.5 text-placeholder" strokeWidth={1.5} />
|
||||||
<Combobox.Input
|
<Combobox.Input
|
||||||
as="input"
|
as="input"
|
||||||
ref={inputRef}
|
ref={inputRef}
|
||||||
className="w-full bg-transparent py-1 text-11 text-secondary placeholder:text-placeholder focus:outline-none"
|
className="w-full bg-transparent py-0 text-12 text-secondary placeholder:text-placeholder focus:outline-none"
|
||||||
value={query}
|
value={query}
|
||||||
onChange={(e) => setQuery(e.target.value)}
|
onChange={(e) => setQuery(e.target.value)}
|
||||||
placeholder={t("common.search.label")}
|
placeholder={t("common.search.label")}
|
||||||
|
|
@ -144,7 +145,7 @@ export const CycleOptions = observer(function CycleOptions(props: CycleOptionsPr
|
||||||
onKeyDown={searchInputKeyDown}
|
onKeyDown={searchInputKeyDown}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className="mt-2 max-h-48 space-y-1 overflow-y-scroll">
|
<div className="vertical-scrollbar mt-2 max-h-48 space-y-1 overflow-y-auto pr-1">
|
||||||
{filteredOptions ? (
|
{filteredOptions ? (
|
||||||
filteredOptions.length > 0 ? (
|
filteredOptions.length > 0 ? (
|
||||||
filteredOptions.map((option) => (
|
filteredOptions.map((option) => (
|
||||||
|
|
@ -152,9 +153,11 @@ export const CycleOptions = observer(function CycleOptions(props: CycleOptionsPr
|
||||||
key={option.value}
|
key={option.value}
|
||||||
value={option.value}
|
value={option.value}
|
||||||
className={({ active, selected }) =>
|
className={({ active, selected }) =>
|
||||||
`flex w-full cursor-pointer items-center justify-between gap-2 truncate rounded-sm px-1 py-1.5 select-none ${
|
cn("nodedc-dropdown-option", {
|
||||||
active ? "bg-layer-transparent-hover" : ""
|
"bg-white/6": active,
|
||||||
} ${selected ? "text-primary" : "text-secondary"}`
|
"text-primary": selected,
|
||||||
|
"text-secondary": !selected,
|
||||||
|
})
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
{({ selected }) => (
|
{({ selected }) => (
|
||||||
|
|
@ -166,10 +169,10 @@ export const CycleOptions = observer(function CycleOptions(props: CycleOptionsPr
|
||||||
</Combobox.Option>
|
</Combobox.Option>
|
||||||
))
|
))
|
||||||
) : (
|
) : (
|
||||||
<p className="px-1.5 py-1 text-placeholder italic">{t("common.search.no_matches_found")}</p>
|
<p className="px-2 py-1 text-placeholder italic">{t("common.search.no_matches_found")}</p>
|
||||||
)
|
)
|
||||||
) : (
|
) : (
|
||||||
<p className="px-1.5 py-1 text-placeholder italic">{t("common.loading")}</p>
|
<p className="px-2 py-1 text-placeholder italic">{t("common.loading")}</p>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -234,17 +234,17 @@ export const EstimateDropdown = observer(function EstimateDropdown(props: Props)
|
||||||
{isOpen && (
|
{isOpen && (
|
||||||
<Combobox.Options className="fixed z-10" static>
|
<Combobox.Options className="fixed z-10" static>
|
||||||
<div
|
<div
|
||||||
className="my-1 w-48 rounded-sm border-[0.5px] border-strong bg-surface-1 px-2 py-2.5 text-11 shadow-raised-200 focus:outline-none"
|
className="nodedc-dropdown-surface z-30 my-1 w-52"
|
||||||
ref={setPopperElement}
|
ref={setPopperElement}
|
||||||
style={styles.popper}
|
style={styles.popper}
|
||||||
{...attributes.popper}
|
{...attributes.popper}
|
||||||
>
|
>
|
||||||
<div className="flex items-center gap-1.5 rounded-sm border border-subtle bg-surface-2 px-2">
|
<div className="nodedc-dropdown-search">
|
||||||
<SearchIcon className="h-3.5 w-3.5 text-placeholder" strokeWidth={1.5} />
|
<SearchIcon className="h-3.5 w-3.5 text-placeholder" strokeWidth={1.5} />
|
||||||
<Combobox.Input
|
<Combobox.Input
|
||||||
as="input"
|
as="input"
|
||||||
ref={inputRef}
|
ref={inputRef}
|
||||||
className="w-full bg-transparent py-1 text-11 text-secondary placeholder:text-placeholder focus:outline-none"
|
className="w-full bg-transparent py-0 text-12 text-secondary placeholder:text-placeholder focus:outline-none"
|
||||||
value={query}
|
value={query}
|
||||||
onChange={(e) => setQuery(e.target.value)}
|
onChange={(e) => setQuery(e.target.value)}
|
||||||
placeholder={t("common.search.placeholder")}
|
placeholder={t("common.search.placeholder")}
|
||||||
|
|
@ -252,11 +252,9 @@ export const EstimateDropdown = observer(function EstimateDropdown(props: Props)
|
||||||
onKeyDown={searchInputKeyDown}
|
onKeyDown={searchInputKeyDown}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className="mt-2 max-h-48 space-y-1 overflow-y-scroll">
|
<div className="vertical-scrollbar mt-2 max-h-48 space-y-1 overflow-y-auto pr-1">
|
||||||
{currentActiveEstimateId === undefined ? (
|
{currentActiveEstimateId === undefined ? (
|
||||||
<div
|
<div className="nodedc-dropdown-option cursor-default">
|
||||||
className={`flex w-full cursor-pointer items-center justify-between gap-2 truncate rounded-sm px-1 py-1.5 text-secondary select-none`}
|
|
||||||
>
|
|
||||||
{/* NOTE: This condition renders when estimates are not enabled for the project */}
|
{/* NOTE: This condition renders when estimates are not enabled for the project */}
|
||||||
<div className="flex flex-grow items-center gap-2">
|
<div className="flex flex-grow items-center gap-2">
|
||||||
<EstimatePropertyIcon className="h-3 w-3 flex-shrink-0" />
|
<EstimatePropertyIcon className="h-3 w-3 flex-shrink-0" />
|
||||||
|
|
@ -272,9 +270,9 @@ export const EstimateDropdown = observer(function EstimateDropdown(props: Props)
|
||||||
{({ active, selected }) => (
|
{({ active, selected }) => (
|
||||||
<div
|
<div
|
||||||
className={cn(
|
className={cn(
|
||||||
"flex w-full cursor-pointer items-center justify-between gap-2 truncate rounded-sm px-1 py-1.5 select-none",
|
"nodedc-dropdown-option",
|
||||||
{
|
{
|
||||||
"bg-layer-transparent-hover": active,
|
"bg-white/6": active,
|
||||||
"text-primary": selected,
|
"text-primary": selected,
|
||||||
"text-secondary": !selected,
|
"text-secondary": !selected,
|
||||||
}
|
}
|
||||||
|
|
@ -287,10 +285,10 @@ export const EstimateDropdown = observer(function EstimateDropdown(props: Props)
|
||||||
</Combobox.Option>
|
</Combobox.Option>
|
||||||
))
|
))
|
||||||
) : (
|
) : (
|
||||||
<p className="px-1.5 py-1 text-placeholder italic">{t("common.search.no_matching_results")}</p>
|
<p className="px-2 py-1 text-placeholder italic">{t("common.search.no_matching_results")}</p>
|
||||||
)
|
)
|
||||||
) : (
|
) : (
|
||||||
<p className="px-1.5 py-1 text-placeholder italic">{t("common.loading")}</p>
|
<p className="px-2 py-1 text-placeholder italic">{t("common.loading")}</p>
|
||||||
)}
|
)}
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
|
|
|
||||||
|
|
@ -210,14 +210,14 @@ export const WorkItemStateDropdownBase = observer(function WorkItemStateDropdown
|
||||||
renderByDefault={renderByDefault}
|
renderByDefault={renderByDefault}
|
||||||
>
|
>
|
||||||
{isOpen && (
|
{isOpen && (
|
||||||
<Combobox.Options className="fixed z-10" static>
|
<Combobox.Options className="fixed z-30" static>
|
||||||
<div
|
<div
|
||||||
className="nodedc-glass-modal nodedc-glass-popup-surface my-1 w-52 rounded-[1.25rem] border-0 px-3 py-3 text-12 shadow-none outline-none"
|
className="nodedc-dropdown-surface z-30 my-1 w-52"
|
||||||
ref={setPopperElement}
|
ref={setPopperElement}
|
||||||
style={styles.popper}
|
style={styles.popper}
|
||||||
{...attributes.popper}
|
{...attributes.popper}
|
||||||
>
|
>
|
||||||
<div className="flex items-center gap-1.5 rounded-[0.95rem] border-0 bg-white/5 px-3 py-2 outline-none">
|
<div className="nodedc-dropdown-search">
|
||||||
<SearchIcon className="h-3.5 w-3.5 text-placeholder" strokeWidth={1.5} />
|
<SearchIcon className="h-3.5 w-3.5 text-placeholder" strokeWidth={1.5} />
|
||||||
<Combobox.Input
|
<Combobox.Input
|
||||||
as="input"
|
as="input"
|
||||||
|
|
@ -230,7 +230,7 @@ export const WorkItemStateDropdownBase = observer(function WorkItemStateDropdown
|
||||||
onKeyDown={searchInputKeyDown}
|
onKeyDown={searchInputKeyDown}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className="mt-2 max-h-56 space-y-1 overflow-y-auto">
|
<div className="vertical-scrollbar mt-2 max-h-56 space-y-1 overflow-y-auto pr-1">
|
||||||
{filteredOptions ? (
|
{filteredOptions ? (
|
||||||
filteredOptions.length > 0 ? (
|
filteredOptions.length > 0 ? (
|
||||||
filteredOptions.map((option) => (
|
filteredOptions.map((option) => (
|
||||||
|
|
@ -238,11 +238,11 @@ export const WorkItemStateDropdownBase = observer(function WorkItemStateDropdown
|
||||||
key={option.value}
|
key={option.value}
|
||||||
value={option.value}
|
value={option.value}
|
||||||
className={({ active, selected }) =>
|
className={({ active, selected }) =>
|
||||||
cn(
|
cn("nodedc-dropdown-option", {
|
||||||
`flex w-full cursor-pointer items-center justify-between gap-2 truncate rounded-[0.9rem] px-2 py-2 select-none outline-none ${
|
"bg-white/6": active,
|
||||||
active ? "bg-white/6" : ""
|
"text-primary": selected,
|
||||||
} ${selected ? "text-primary" : "text-secondary"}`
|
"text-secondary": !selected,
|
||||||
)
|
})
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
{({ selected }) => (
|
{({ selected }) => (
|
||||||
|
|
@ -254,10 +254,10 @@ export const WorkItemStateDropdownBase = observer(function WorkItemStateDropdown
|
||||||
</Combobox.Option>
|
</Combobox.Option>
|
||||||
))
|
))
|
||||||
) : (
|
) : (
|
||||||
<p className="px-1.5 py-1 text-placeholder italic">{t("no_matching_results")}</p>
|
<p className="px-2 py-1 text-placeholder italic">{t("no_matching_results")}</p>
|
||||||
)
|
)
|
||||||
) : (
|
) : (
|
||||||
<p className="px-1.5 py-1 text-placeholder italic">{t("loading")}</p>
|
<p className="px-2 py-1 text-placeholder italic">{t("loading")}</p>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -21,10 +21,11 @@ type TLayoutDropDown = {
|
||||||
onChange: (value: EIssueLayoutTypes) => void;
|
onChange: (value: EIssueLayoutTypes) => void;
|
||||||
value: EIssueLayoutTypes;
|
value: EIssueLayoutTypes;
|
||||||
disabledLayouts?: EIssueLayoutTypes[];
|
disabledLayouts?: EIssueLayoutTypes[];
|
||||||
|
buttonContainerClassName?: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const LayoutDropDown = observer(function LayoutDropDown(props: TLayoutDropDown) {
|
export const LayoutDropDown = observer(function LayoutDropDown(props: TLayoutDropDown) {
|
||||||
const { onChange, value = EIssueLayoutTypes.LIST, disabledLayouts = [] } = props;
|
const { onChange, value = EIssueLayoutTypes.LIST, disabledLayouts = [], buttonContainerClassName } = props;
|
||||||
// plane i18n
|
// plane i18n
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
// derived values
|
// derived values
|
||||||
|
|
@ -74,7 +75,7 @@ export const LayoutDropDown = observer(function LayoutDropDown(props: TLayoutDro
|
||||||
value={value?.toString()}
|
value={value?.toString()}
|
||||||
keyExtractor={keyExtractor}
|
keyExtractor={keyExtractor}
|
||||||
options={options}
|
options={options}
|
||||||
buttonContainerClassName={cn(getIconButtonStyling("secondary", "lg"), "w-auto px-2")}
|
buttonContainerClassName={cn(getIconButtonStyling("secondary", "lg"), "w-auto px-2", buttonContainerClassName)}
|
||||||
buttonContent={buttonContent}
|
buttonContent={buttonContent}
|
||||||
renderItem={itemContent}
|
renderItem={itemContent}
|
||||||
disableSearch
|
disableSearch
|
||||||
|
|
|
||||||
|
|
@ -115,17 +115,17 @@ export const ModuleOptions = observer(function ModuleOptions(props: Props) {
|
||||||
return (
|
return (
|
||||||
<Combobox.Options className="fixed z-10" static>
|
<Combobox.Options className="fixed z-10" static>
|
||||||
<div
|
<div
|
||||||
className="my-1 w-48 rounded-sm border-[0.5px] border-strong bg-surface-1 px-2 py-2.5 text-11 shadow-raised-200 focus:outline-none"
|
className="nodedc-dropdown-surface z-30 my-1 w-52"
|
||||||
ref={setPopperElement}
|
ref={setPopperElement}
|
||||||
style={styles.popper}
|
style={styles.popper}
|
||||||
{...attributes.popper}
|
{...attributes.popper}
|
||||||
>
|
>
|
||||||
<div className="flex items-center gap-1.5 rounded-sm border border-subtle bg-surface-2 px-2">
|
<div className="nodedc-dropdown-search">
|
||||||
<SearchIcon className="h-3.5 w-3.5 text-placeholder" strokeWidth={1.5} />
|
<SearchIcon className="h-3.5 w-3.5 text-placeholder" strokeWidth={1.5} />
|
||||||
<Combobox.Input
|
<Combobox.Input
|
||||||
as="input"
|
as="input"
|
||||||
ref={inputRef}
|
ref={inputRef}
|
||||||
className="w-full bg-transparent py-1 text-11 text-secondary placeholder:text-placeholder focus:outline-none"
|
className="w-full bg-transparent py-0 text-12 text-secondary placeholder:text-placeholder focus:outline-none"
|
||||||
value={query}
|
value={query}
|
||||||
onChange={(e) => setQuery(e.target.value)}
|
onChange={(e) => setQuery(e.target.value)}
|
||||||
placeholder={t("common.search.label")}
|
placeholder={t("common.search.label")}
|
||||||
|
|
@ -133,7 +133,7 @@ export const ModuleOptions = observer(function ModuleOptions(props: Props) {
|
||||||
onKeyDown={searchInputKeyDown}
|
onKeyDown={searchInputKeyDown}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className="mt-2 max-h-48 space-y-1 overflow-y-scroll">
|
<div className="vertical-scrollbar mt-2 max-h-48 space-y-1 overflow-y-auto pr-1">
|
||||||
{filteredOptions ? (
|
{filteredOptions ? (
|
||||||
filteredOptions.length > 0 ? (
|
filteredOptions.length > 0 ? (
|
||||||
filteredOptions.map((option) => (
|
filteredOptions.map((option) => (
|
||||||
|
|
@ -142,9 +142,9 @@ export const ModuleOptions = observer(function ModuleOptions(props: Props) {
|
||||||
value={option.value}
|
value={option.value}
|
||||||
className={({ active, selected }) =>
|
className={({ active, selected }) =>
|
||||||
cn(
|
cn(
|
||||||
"flex w-full cursor-pointer items-center justify-between gap-2 truncate rounded-sm px-1 py-1.5 select-none",
|
"nodedc-dropdown-option",
|
||||||
{
|
{
|
||||||
"bg-layer-transparent-hover": active,
|
"bg-white/6": active,
|
||||||
"text-primary": selected,
|
"text-primary": selected,
|
||||||
"text-secondary": !selected,
|
"text-secondary": !selected,
|
||||||
}
|
}
|
||||||
|
|
@ -160,10 +160,10 @@ export const ModuleOptions = observer(function ModuleOptions(props: Props) {
|
||||||
</Combobox.Option>
|
</Combobox.Option>
|
||||||
))
|
))
|
||||||
) : (
|
) : (
|
||||||
<p className="px-1.5 py-1 text-placeholder italic">{t("common.search.no_matching_results")}</p>
|
<p className="px-2 py-1 text-placeholder italic">{t("common.search.no_matching_results")}</p>
|
||||||
)
|
)
|
||||||
) : (
|
) : (
|
||||||
<p className="px-1.5 py-1 text-placeholder italic">{t("common.loading")}</p>
|
<p className="px-2 py-1 text-placeholder italic">{t("common.loading")}</p>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -216,12 +216,12 @@ export const WorkItemStateDropdownBase = observer(function WorkItemStateDropdown
|
||||||
createPortal(
|
createPortal(
|
||||||
<Combobox.Options data-prevent-outside-click className="fixed z-30" static>
|
<Combobox.Options data-prevent-outside-click className="fixed z-30" static>
|
||||||
<div
|
<div
|
||||||
className="nodedc-glass-modal nodedc-glass-popup-surface my-1 w-52 rounded-[1.25rem] border-0 px-3 py-3 text-12 shadow-none outline-none"
|
className="nodedc-dropdown-surface z-30 my-1 w-52"
|
||||||
ref={setPopperElement}
|
ref={setPopperElement}
|
||||||
style={styles.popper}
|
style={styles.popper}
|
||||||
{...attributes.popper}
|
{...attributes.popper}
|
||||||
>
|
>
|
||||||
<div className="flex items-center gap-1.5 rounded-[0.95rem] border-0 bg-white/5 px-3 py-2 outline-none">
|
<div className="nodedc-dropdown-search">
|
||||||
<SearchIcon className="h-3.5 w-3.5 text-placeholder" strokeWidth={1.5} />
|
<SearchIcon className="h-3.5 w-3.5 text-placeholder" strokeWidth={1.5} />
|
||||||
<Combobox.Input
|
<Combobox.Input
|
||||||
as="input"
|
as="input"
|
||||||
|
|
@ -234,7 +234,7 @@ export const WorkItemStateDropdownBase = observer(function WorkItemStateDropdown
|
||||||
onKeyDown={searchInputKeyDown}
|
onKeyDown={searchInputKeyDown}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className="mt-2 max-h-56 space-y-1 overflow-y-auto">
|
<div className="vertical-scrollbar mt-2 max-h-56 space-y-1 overflow-y-auto pr-1">
|
||||||
{filteredOptions ? (
|
{filteredOptions ? (
|
||||||
filteredOptions.length > 0 ? (
|
filteredOptions.length > 0 ? (
|
||||||
filteredOptions.map((option) => (
|
filteredOptions.map((option) => (
|
||||||
|
|
@ -242,11 +242,11 @@ export const WorkItemStateDropdownBase = observer(function WorkItemStateDropdown
|
||||||
key={option.value}
|
key={option.value}
|
||||||
value={option.value}
|
value={option.value}
|
||||||
className={({ active, selected }) =>
|
className={({ active, selected }) =>
|
||||||
cn(
|
cn("nodedc-dropdown-option", {
|
||||||
`flex w-full cursor-pointer items-center justify-between gap-2 truncate rounded-[0.9rem] px-2 py-2 select-none outline-none ${
|
"bg-white/6": active,
|
||||||
active ? "bg-white/6" : ""
|
"text-primary": selected,
|
||||||
} ${selected ? "text-primary" : "text-secondary"}`
|
"text-secondary": !selected,
|
||||||
)
|
})
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
{({ selected }) => (
|
{({ selected }) => (
|
||||||
|
|
@ -258,10 +258,10 @@ export const WorkItemStateDropdownBase = observer(function WorkItemStateDropdown
|
||||||
</Combobox.Option>
|
</Combobox.Option>
|
||||||
))
|
))
|
||||||
) : (
|
) : (
|
||||||
<p className="px-1.5 py-1 text-placeholder italic">{t("no_matching_results")}</p>
|
<p className="px-2 py-1 text-placeholder italic">{t("no_matching_results")}</p>
|
||||||
)
|
)
|
||||||
) : (
|
) : (
|
||||||
<p className="px-1.5 py-1 text-placeholder italic">{t("loading")}</p>
|
<p className="px-2 py-1 text-placeholder italic">{t("loading")}</p>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -8,12 +8,12 @@ import { useState } from "react";
|
||||||
import { omit } from "lodash-es";
|
import { omit } from "lodash-es";
|
||||||
import { observer } from "mobx-react";
|
import { observer } from "mobx-react";
|
||||||
import { useParams } from "next/navigation";
|
import { useParams } from "next/navigation";
|
||||||
import { Ellipsis } from "lucide-react";
|
import { MoreHorizontal } from "lucide-react";
|
||||||
// plane imports
|
// plane imports
|
||||||
import { ARCHIVABLE_STATE_GROUPS, EUserPermissions, EUserPermissionsLevel } from "@plane/constants";
|
import { ARCHIVABLE_STATE_GROUPS, EUserPermissions, EUserPermissionsLevel } from "@plane/constants";
|
||||||
import type { TIssue } from "@plane/types";
|
import type { TIssue } from "@plane/types";
|
||||||
import { EIssuesStoreType } from "@plane/types";
|
import { EIssuesStoreType } from "@plane/types";
|
||||||
import { ActionDropdown, ContextMenu } from "@plane/ui";
|
import { ActionDropdown, ContextMenu, cn } from "@plane/ui";
|
||||||
// hooks
|
// hooks
|
||||||
import { useIssues } from "@/hooks/store/use-issues";
|
import { useIssues } from "@/hooks/store/use-issues";
|
||||||
import { useProject } from "@/hooks/store/use-project";
|
import { useProject } from "@/hooks/store/use-project";
|
||||||
|
|
@ -240,15 +240,17 @@ export const WorkItemDetailQuickActions = observer(function WorkItemDetailQuickA
|
||||||
<ContextMenu parentRef={parentRef} items={CONTEXT_MENU_ITEMS} />
|
<ContextMenu parentRef={parentRef} items={CONTEXT_MENU_ITEMS} />
|
||||||
<ActionDropdown
|
<ActionDropdown
|
||||||
button={
|
button={
|
||||||
<div
|
<button
|
||||||
className={
|
type="button"
|
||||||
buttonClassName ??
|
className={cn(
|
||||||
"flex size-10 items-center justify-center rounded-[18px] border-transparent bg-layer-2/80 text-secondary shadow-none backdrop-blur-xl hover:bg-layer-2-active"
|
"inline-flex size-10 shrink-0 items-center justify-center rounded-[18px] border-transparent bg-layer-2/80 text-secondary shadow-none backdrop-blur-xl transition-colors hover:bg-layer-2-active focus-visible:outline-none",
|
||||||
}
|
buttonClassName
|
||||||
|
)}
|
||||||
>
|
>
|
||||||
<Ellipsis className="h-4 w-4" />
|
<MoreHorizontal className="pointer-events-none h-4 w-4 shrink-0" />
|
||||||
</div>
|
</button>
|
||||||
}
|
}
|
||||||
|
buttonAsChild
|
||||||
items={MENU_ITEMS}
|
items={MENU_ITEMS}
|
||||||
placement={placements}
|
placement={placements}
|
||||||
portalElement={portalElement}
|
portalElement={portalElement}
|
||||||
|
|
|
||||||
|
|
@ -42,7 +42,7 @@ export const SpreadsheetAssigneeColumn = observer(function SpreadsheetAssigneeCo
|
||||||
buttonVariant={
|
buttonVariant={
|
||||||
issue?.assignee_ids && issue.assignee_ids.length > 1 ? "transparent-without-text" : "transparent-with-text"
|
issue?.assignee_ids && issue.assignee_ids.length > 1 ? "transparent-without-text" : "transparent-with-text"
|
||||||
}
|
}
|
||||||
buttonClassName="text-left rounded-none group-[.selected-issue-row]:bg-accent-primary/5 group-[.selected-issue-row]:hover:bg-accent-primary/10 px-page-x"
|
buttonClassName="nodedc-spreadsheet-cell-button text-left"
|
||||||
buttonContainerClassName="w-full"
|
buttonContainerClassName="w-full"
|
||||||
optionsClassName="z-[9]"
|
optionsClassName="z-[9]"
|
||||||
onClose={onClose}
|
onClose={onClose}
|
||||||
|
|
|
||||||
|
|
@ -47,8 +47,8 @@ export const SpreadsheetCycleColumn = observer(function SpreadsheetCycleColumn(p
|
||||||
disabled={disabled}
|
disabled={disabled}
|
||||||
placeholder="Select cycle"
|
placeholder="Select cycle"
|
||||||
buttonVariant="transparent-with-text"
|
buttonVariant="transparent-with-text"
|
||||||
buttonContainerClassName="w-full relative flex items-center p-2 group-[.selected-issue-row]:bg-accent-primary/5 group-[.selected-issue-row]:hover:bg-accent-primary/10 px-page-x"
|
buttonContainerClassName="w-full"
|
||||||
buttonClassName="relative leading-4 h-4.5 bg-transparent hover:bg-transparent px-0"
|
buttonClassName="nodedc-spreadsheet-cell-button text-left"
|
||||||
onClose={onClose}
|
onClose={onClose}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -52,7 +52,7 @@ export const SpreadsheetDueDateColumn = observer(function SpreadsheetDueDateColu
|
||||||
buttonVariant="transparent-with-text"
|
buttonVariant="transparent-with-text"
|
||||||
buttonContainerClassName="w-full"
|
buttonContainerClassName="w-full"
|
||||||
buttonClassName={cn(
|
buttonClassName={cn(
|
||||||
"rounded-none px-page-x text-left group-[.selected-issue-row]:bg-accent-primary/5 group-[.selected-issue-row]:hover:bg-accent-primary/10",
|
"nodedc-spreadsheet-cell-button text-left",
|
||||||
{
|
{
|
||||||
"text-danger-primary": shouldHighlightIssueDueDate(issue.target_date, stateDetails?.group),
|
"text-danger-primary": shouldHighlightIssueDueDate(issue.target_date, stateDetails?.group),
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -31,7 +31,7 @@ export const SpreadsheetEstimateColumn = observer(function SpreadsheetEstimateCo
|
||||||
projectId={issue.project_id ?? undefined}
|
projectId={issue.project_id ?? undefined}
|
||||||
disabled={disabled}
|
disabled={disabled}
|
||||||
buttonVariant="transparent-with-text"
|
buttonVariant="transparent-with-text"
|
||||||
buttonClassName="text-left rounded-none group-[.selected-issue-row]:bg-accent-primary/5 group-[.selected-issue-row]:hover:bg-accent-primary/10 px-page-x"
|
buttonClassName="nodedc-spreadsheet-cell-button text-left"
|
||||||
buttonContainerClassName="w-full"
|
buttonContainerClassName="w-full"
|
||||||
onClose={onClose}
|
onClose={onClose}
|
||||||
/>
|
/>
|
||||||
|
|
|
||||||
|
|
@ -35,7 +35,7 @@ export const SpreadsheetLabelColumn = observer(function SpreadsheetLabelColumn(p
|
||||||
defaultOptions={defaultLabelOptions}
|
defaultOptions={defaultLabelOptions}
|
||||||
onChange={(data) => onChange(issue, { label_ids: data }, { changed_property: "labels", change_details: data })}
|
onChange={(data) => onChange(issue, { label_ids: data }, { changed_property: "labels", change_details: data })}
|
||||||
className="h-full w-full"
|
className="h-full w-full"
|
||||||
buttonClassName="px-page-x w-full h-full group-[.selected-issue-row]:bg-accent-primary/5 group-[.selected-issue-row]:hover:bg-accent-primary/10 rounded-none"
|
buttonClassName="nodedc-spreadsheet-cell-button text-left"
|
||||||
hideDropdownArrow
|
hideDropdownArrow
|
||||||
maxRender={1}
|
maxRender={1}
|
||||||
disabled={disabled}
|
disabled={disabled}
|
||||||
|
|
|
||||||
|
|
@ -55,8 +55,8 @@ export const SpreadsheetModuleColumn = observer(function SpreadsheetModuleColumn
|
||||||
disabled={disabled}
|
disabled={disabled}
|
||||||
placeholder="Select modules"
|
placeholder="Select modules"
|
||||||
buttonVariant="transparent-with-text"
|
buttonVariant="transparent-with-text"
|
||||||
buttonContainerClassName="w-full relative flex items-center p-2 group-[.selected-issue-row]:bg-accent-primary/5 group-[.selected-issue-row]:hover:bg-accent-primary/10 px-page-x"
|
buttonContainerClassName="w-full"
|
||||||
buttonClassName="relative leading-4 h-4.5 bg-transparent hover:bg-transparent !px-0"
|
buttonClassName="nodedc-spreadsheet-cell-button text-left"
|
||||||
onClose={onClose}
|
onClose={onClose}
|
||||||
multiple
|
multiple
|
||||||
showCount
|
showCount
|
||||||
|
|
|
||||||
|
|
@ -28,7 +28,7 @@ export const SpreadsheetPriorityColumn = observer(function SpreadsheetPriorityCo
|
||||||
onChange={(data) => onChange(issue, { priority: data }, { changed_property: "priority", change_details: data })}
|
onChange={(data) => onChange(issue, { priority: data }, { changed_property: "priority", change_details: data })}
|
||||||
disabled={disabled}
|
disabled={disabled}
|
||||||
buttonVariant="transparent-with-text"
|
buttonVariant="transparent-with-text"
|
||||||
buttonClassName="text-left rounded-none group-[.selected-issue-row]:bg-accent-primary/5 group-[.selected-issue-row]:hover:bg-accent-primary/10 px-page-x"
|
buttonClassName="nodedc-spreadsheet-cell-button text-left"
|
||||||
buttonContainerClassName="w-full"
|
buttonContainerClassName="w-full"
|
||||||
onClose={onClose}
|
onClose={onClose}
|
||||||
/>
|
/>
|
||||||
|
|
|
||||||
|
|
@ -44,7 +44,7 @@ export const SpreadsheetStartDateColumn = observer(function SpreadsheetStartDate
|
||||||
placeholder="Start date"
|
placeholder="Start date"
|
||||||
icon={<StartDatePropertyIcon className="h-3 w-3 flex-shrink-0" />}
|
icon={<StartDatePropertyIcon className="h-3 w-3 flex-shrink-0" />}
|
||||||
buttonVariant="transparent-with-text"
|
buttonVariant="transparent-with-text"
|
||||||
buttonClassName="text-left rounded-none group-[.selected-issue-row]:bg-accent-primary/5 group-[.selected-issue-row]:hover:bg-accent-primary/10 px-page-x"
|
buttonClassName="nodedc-spreadsheet-cell-button text-left"
|
||||||
buttonContainerClassName="w-full"
|
buttonContainerClassName="w-full"
|
||||||
optionsClassName="z-[9]"
|
optionsClassName="z-[9]"
|
||||||
onClose={onClose}
|
onClose={onClose}
|
||||||
|
|
|
||||||
|
|
@ -29,7 +29,7 @@ export const SpreadsheetStateColumn = observer(function SpreadsheetStateColumn(p
|
||||||
onChange={(data) => onChange(issue, { state_id: data }, { changed_property: "state", change_details: data })}
|
onChange={(data) => onChange(issue, { state_id: data }, { changed_property: "state", change_details: data })}
|
||||||
disabled={disabled}
|
disabled={disabled}
|
||||||
buttonVariant="transparent-with-text"
|
buttonVariant="transparent-with-text"
|
||||||
buttonClassName="text-left rounded-none group-[.selected-issue-row]:bg-accent-primary/5 group-[.selected-issue-row]:hover:bg-accent-primary/10 px-page-x"
|
buttonClassName="nodedc-spreadsheet-cell-button text-left"
|
||||||
buttonContainerClassName="w-full"
|
buttonContainerClassName="w-full"
|
||||||
onClose={onClose}
|
onClose={onClose}
|
||||||
showTooltip
|
showTooltip
|
||||||
|
|
|
||||||
|
|
@ -126,7 +126,7 @@ export const DraftIssueBlock = observer(function DraftIssueBlock(props: Props) {
|
||||||
/>
|
/>
|
||||||
<div
|
<div
|
||||||
id={`issue-${issue.id}`}
|
id={`issue-${issue.id}`}
|
||||||
className="relative w-full cursor-pointer border-b border-b-subtle-1"
|
className="relative mb-3 w-full cursor-pointer last:mb-0"
|
||||||
onDoubleClick={() => {
|
onDoubleClick={() => {
|
||||||
setIssueToEdit(issue);
|
setIssueToEdit(issue);
|
||||||
setCreateUpdateIssueModal(true);
|
setCreateUpdateIssueModal(true);
|
||||||
|
|
@ -135,7 +135,7 @@ export const DraftIssueBlock = observer(function DraftIssueBlock(props: Props) {
|
||||||
<Row
|
<Row
|
||||||
ref={issueRef}
|
ref={issueRef}
|
||||||
className={cn(
|
className={cn(
|
||||||
"group/list-block relative flex min-h-11 flex-col gap-3 bg-layer-transparent py-3 text-13 transition-colors hover:bg-layer-transparent-hover",
|
"nodedc-workspace-list-row group/list-block relative flex min-h-[5.25rem] flex-col gap-3 px-4 py-4 text-13",
|
||||||
{
|
{
|
||||||
"md:flex-row md:items-center": isSidebarCollapsed,
|
"md:flex-row md:items-center": isSidebarCollapsed,
|
||||||
"lg:flex-row lg:items-center": !isSidebarCollapsed,
|
"lg:flex-row lg:items-center": !isSidebarCollapsed,
|
||||||
|
|
@ -170,7 +170,7 @@ export const DraftIssueBlock = observer(function DraftIssueBlock(props: Props) {
|
||||||
|
|
||||||
{/* quick actions */}
|
{/* quick actions */}
|
||||||
<div
|
<div
|
||||||
className={cn("block rounded-sm border border-strong", {
|
className={cn("block", {
|
||||||
"md:hidden": isSidebarCollapsed,
|
"md:hidden": isSidebarCollapsed,
|
||||||
"lg:hidden": !isSidebarCollapsed,
|
"lg:hidden": !isSidebarCollapsed,
|
||||||
})}
|
})}
|
||||||
|
|
|
||||||
|
|
@ -35,7 +35,7 @@ export const WorkspaceDraftEmptyState = observer(function WorkspaceDraftEmptySta
|
||||||
onClose={() => setIsDraftIssueModalOpen(false)}
|
onClose={() => setIsDraftIssueModalOpen(false)}
|
||||||
isDraft
|
isDraft
|
||||||
/>
|
/>
|
||||||
<div className="relative h-full w-full overflow-y-auto">
|
<div className="nodedc-external-section relative h-full w-full overflow-y-auto rounded-[1.75rem] px-4 py-4 md:px-6 md:py-6">
|
||||||
<EmptyStateDetailed
|
<EmptyStateDetailed
|
||||||
title={t("workspace_empty_state.drafts.title")}
|
title={t("workspace_empty_state.drafts.title")}
|
||||||
description={t("workspace_empty_state.drafts.description")}
|
description={t("workspace_empty_state.drafts.description")}
|
||||||
|
|
|
||||||
|
|
@ -87,7 +87,7 @@ export const WorkspaceDraftIssuesRoot = observer(function WorkspaceDraftIssuesRo
|
||||||
if (issueIds.length <= 0) return <WorkspaceDraftEmptyState />;
|
if (issueIds.length <= 0) return <WorkspaceDraftEmptyState />;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="relative">
|
<div className="relative flex flex-col gap-3">
|
||||||
<div className="relative">
|
<div className="relative">
|
||||||
{issueIds.map((issueId: string) => (
|
{issueIds.map((issueId: string) => (
|
||||||
<DraftIssueBlock key={issueId} workspaceSlug={workspaceSlug} issueId={issueId} />
|
<DraftIssueBlock key={issueId} workspaceSlug={workspaceSlug} issueId={issueId} />
|
||||||
|
|
@ -100,8 +100,8 @@ export const WorkspaceDraftIssuesRoot = observer(function WorkspaceDraftIssuesRo
|
||||||
<WorkspaceDraftIssuesLoader items={1} />
|
<WorkspaceDraftIssuesLoader items={1} />
|
||||||
) : (
|
) : (
|
||||||
<div
|
<div
|
||||||
className={cn("h-11 border-b border-subtle bg-surface-1 p-3 pl-6 text-13 font-medium transition-all", {
|
className={cn("h-11 text-13 font-medium transition-all", {
|
||||||
"cursor-pointer text-accent-primary underline-offset-2 hover:text-accent-secondary hover:underline":
|
"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":
|
||||||
paginationInfo?.next_page_results,
|
paginationInfo?.next_page_results,
|
||||||
})}
|
})}
|
||||||
onClick={handleNextIssues}
|
onClick={handleNextIssues}
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,7 @@ import { ArrowDownWideNarrow } from "lucide-react";
|
||||||
// plane imports
|
// plane imports
|
||||||
import { PROJECT_ORDER_BY_OPTIONS } from "@plane/constants";
|
import { PROJECT_ORDER_BY_OPTIONS } from "@plane/constants";
|
||||||
import { useTranslation } from "@plane/i18n";
|
import { useTranslation } from "@plane/i18n";
|
||||||
import { getButtonStyling } from "@plane/propel/button";
|
import { ChevronDownIcon } from "@plane/propel/icons";
|
||||||
import type { TProjectOrderByOptions } from "@plane/types";
|
import type { TProjectOrderByOptions } from "@plane/types";
|
||||||
import { SortingDropdown } from "@/components/common/sorting-dropdown";
|
import { SortingDropdown } from "@/components/common/sorting-dropdown";
|
||||||
|
|
||||||
|
|
@ -32,13 +32,15 @@ export function ProjectOrderByDropdown(props: Props) {
|
||||||
return (
|
return (
|
||||||
<SortingDropdown
|
<SortingDropdown
|
||||||
menuButton={
|
menuButton={
|
||||||
<div className={`${isMobile ? "flex w-full justify-center" : ""}`}>
|
<div className={isMobile ? "flex w-full justify-center" : ""}>
|
||||||
<div className={getButtonStyling("secondary", "lg")}>
|
<div className="nodedc-toolbar-pill flex min-h-[2.5rem] items-center gap-2 px-4 py-0 text-13 font-medium">
|
||||||
<ArrowDownWideNarrow className="size-3.5 shrink-0" strokeWidth={2} />
|
<ArrowDownWideNarrow className="size-3.5 shrink-0" strokeWidth={2} />
|
||||||
{orderByDetails && t(orderByDetails?.i18n_label)}
|
<span className="shrink-0">{orderByDetails ? t(orderByDetails.i18n_label) : t("common.order_by.label")}</span>
|
||||||
|
<ChevronDownIcon className="size-3 shrink-0" strokeWidth={2} />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
|
menuButtonWrapperClassName="h-auto w-auto border-none bg-transparent p-0 hover:bg-transparent"
|
||||||
placement="bottom-end"
|
placement="bottom-end"
|
||||||
title={t("common.order_by.label")}
|
title={t("common.order_by.label")}
|
||||||
sections={[
|
sections={[
|
||||||
|
|
|
||||||
|
|
@ -44,7 +44,7 @@ export const ProjectSearch = observer(function ProjectSearch() {
|
||||||
<IconButton
|
<IconButton
|
||||||
variant="ghost"
|
variant="ghost"
|
||||||
size="lg"
|
size="lg"
|
||||||
className="-mr-1"
|
className="nodedc-toolbar-icon-button"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
setIsSearchOpen(true);
|
setIsSearchOpen(true);
|
||||||
inputRef.current?.focus();
|
inputRef.current?.focus();
|
||||||
|
|
@ -54,9 +54,9 @@ export const ProjectSearch = observer(function ProjectSearch() {
|
||||||
)}
|
)}
|
||||||
<div
|
<div
|
||||||
className={cn(
|
className={cn(
|
||||||
"ml-auto flex w-0 items-center justify-start gap-1 overflow-hidden rounded-md border border-transparent bg-surface-1 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,
|
||||||
}
|
}
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
|
|
@ -72,7 +72,7 @@ export const ProjectSearch = observer(function ProjectSearch() {
|
||||||
{isSearchOpen && (
|
{isSearchOpen && (
|
||||||
<button
|
<button
|
||||||
type="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={() => {
|
onClick={() => {
|
||||||
updateSearchQuery("");
|
updateSearchQuery("");
|
||||||
setIsSearchOpen(false);
|
setIsSearchOpen(false);
|
||||||
|
|
|
||||||
|
|
@ -23,6 +23,7 @@ export type TAddFilterButtonProps<P extends TFilterProperty, E extends TExternal
|
||||||
variant?: TButtonVariant;
|
variant?: TButtonVariant;
|
||||||
size?: TButtonSize;
|
size?: TButtonSize;
|
||||||
className?: string;
|
className?: string;
|
||||||
|
appearance?: "toolbar" | "modal";
|
||||||
defaultOpen?: boolean;
|
defaultOpen?: boolean;
|
||||||
iconConfig?: {
|
iconConfig?: {
|
||||||
shouldShowIcon: boolean;
|
shouldShowIcon: boolean;
|
||||||
|
|
@ -42,6 +43,7 @@ export const AddFilterButton = observer(function AddFilterButton<P extends TFilt
|
||||||
variant = "secondary",
|
variant = "secondary",
|
||||||
size = "base",
|
size = "base",
|
||||||
className,
|
className,
|
||||||
|
appearance = "toolbar",
|
||||||
label,
|
label,
|
||||||
iconConfig = { shouldShowIcon: true },
|
iconConfig = { shouldShowIcon: true },
|
||||||
isDisabled = false,
|
isDisabled = false,
|
||||||
|
|
@ -68,7 +70,11 @@ export const AddFilterButton = observer(function AddFilterButton<P extends TFilt
|
||||||
{...props}
|
{...props}
|
||||||
buttonConfig={{
|
buttonConfig={{
|
||||||
...buttonConfig,
|
...buttonConfig,
|
||||||
className: cn(getButtonStyling(variant, size), "nodedc-toolbar-filter-toggle", className),
|
className: cn(
|
||||||
|
getButtonStyling(variant, size),
|
||||||
|
appearance === "modal" ? "nodedc-work-item-property-button" : "nodedc-toolbar-filter-toggle",
|
||||||
|
className
|
||||||
|
),
|
||||||
}}
|
}}
|
||||||
handleFilterSelect={handleFilterSelect}
|
handleFilterSelect={handleFilterSelect}
|
||||||
customButton={
|
customButton={
|
||||||
|
|
|
||||||
|
|
@ -51,7 +51,7 @@ export const DateRangeFilterValueInput = observer(function DateRangeFilterValueI
|
||||||
mergeDates
|
mergeDates
|
||||||
placeholder={{ from: EMPTY_FILTER_PLACEHOLDER_TEXT }}
|
placeholder={{ from: EMPTY_FILTER_PLACEHOLDER_TEXT }}
|
||||||
buttonVariant="transparent-with-text"
|
buttonVariant="transparent-with-text"
|
||||||
buttonClassName={cn("rounded-none", {
|
buttonClassName={cn("rounded-none !items-center !py-0", {
|
||||||
[COMMON_FILTER_ITEM_BORDER_CLASSNAME]: !isDisabled,
|
[COMMON_FILTER_ITEM_BORDER_CLASSNAME]: !isDisabled,
|
||||||
"text-danger-primary": isIncomplete,
|
"text-danger-primary": isIncomplete,
|
||||||
"hover:bg-surface-1": isDisabled,
|
"hover:bg-surface-1": isDisabled,
|
||||||
|
|
|
||||||
|
|
@ -33,11 +33,12 @@ export const SingleDateFilterValueInput = observer(function SingleDateFilterValu
|
||||||
const formattedDate = value ? renderFormattedPayloadDate(value) : null;
|
const formattedDate = value ? renderFormattedPayloadDate(value) : null;
|
||||||
onChange(formattedDate);
|
onChange(formattedDate);
|
||||||
}}
|
}}
|
||||||
buttonClassName={cn("rounded-none", {
|
buttonClassName={cn("rounded-none !items-center !py-0", {
|
||||||
[COMMON_FILTER_ITEM_BORDER_CLASSNAME]: !isDisabled,
|
[COMMON_FILTER_ITEM_BORDER_CLASSNAME]: !isDisabled,
|
||||||
"text-placeholder": !conditionValue,
|
"text-placeholder": !conditionValue,
|
||||||
"hover:bg-surface-1": isDisabled,
|
"hover:bg-surface-1": isDisabled,
|
||||||
})}
|
})}
|
||||||
|
labelClassName="!text-13 !leading-none"
|
||||||
minDate={config.min}
|
minDate={config.min}
|
||||||
maxDate={config.max}
|
maxDate={config.max}
|
||||||
icon={null}
|
icon={null}
|
||||||
|
|
|
||||||
|
|
@ -10,7 +10,6 @@ import { ListFilterPlus } from "lucide-react";
|
||||||
import { Transition } from "@headlessui/react";
|
import { Transition } from "@headlessui/react";
|
||||||
// plane imports
|
// plane imports
|
||||||
import { useTranslation } from "@plane/i18n";
|
import { useTranslation } from "@plane/i18n";
|
||||||
import { Button } from "@plane/propel/button";
|
|
||||||
import type { IFilterInstance } from "@plane/shared-state";
|
import type { IFilterInstance } from "@plane/shared-state";
|
||||||
import type { TExternalFilter, TFilterProperty } from "@plane/types";
|
import type { TExternalFilter, TFilterProperty } from "@plane/types";
|
||||||
import { cn, EHeaderVariant, Header, Loader } from "@plane/ui";
|
import { cn, EHeaderVariant, Header, Loader } from "@plane/ui";
|
||||||
|
|
@ -56,6 +55,8 @@ export const FiltersRow = observer(function FiltersRow<K extends TFilterProperty
|
||||||
|
|
||||||
const modalButtonConfig: Partial<TAddFilterButtonProps<K, E>["buttonConfig"]> = {
|
const modalButtonConfig: Partial<TAddFilterButtonProps<K, E>["buttonConfig"]> = {
|
||||||
label: !hasAnyConditions ? t("common.filters") : null,
|
label: !hasAnyConditions ? t("common.filters") : null,
|
||||||
|
appearance: "modal",
|
||||||
|
className: "!min-h-7 !px-3 !py-0 text-12 font-medium",
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleUpdate = useCallback(async () => {
|
const handleUpdate = useCallback(async () => {
|
||||||
|
|
@ -92,42 +93,41 @@ export const FiltersRow = observer(function FiltersRow<K extends TFilterProperty
|
||||||
const rightContent = !disabledAllOperations && (
|
const rightContent = !disabledAllOperations && (
|
||||||
<>
|
<>
|
||||||
<ElementTransition show={filter.canClearFilters}>
|
<ElementTransition show={filter.canClearFilters}>
|
||||||
<Button
|
<DockActionButton
|
||||||
variant="secondary"
|
variant="secondary"
|
||||||
className={cn(COMMON_OPERATION_BUTTON_CLASSNAME, "nodedc-filter-clear-button")}
|
className={COMMON_OPERATION_BUTTON_CLASSNAME}
|
||||||
onClick={filter.clearFilters}
|
onClick={filter.clearFilters}
|
||||||
data-ph-element={trackerElements?.clearFilter}
|
data-ph-element={trackerElements?.clearFilter}
|
||||||
>
|
>
|
||||||
{t("common.clear")}
|
{t("common.clear")}
|
||||||
</Button>
|
</DockActionButton>
|
||||||
</ElementTransition>
|
</ElementTransition>
|
||||||
<ElementTransition show={filter.canSaveView}>
|
<ElementTransition show={filter.canSaveView}>
|
||||||
<Button
|
<DockActionButton
|
||||||
variant="secondary"
|
variant="primary"
|
||||||
className={cn(COMMON_OPERATION_BUTTON_CLASSNAME, "nodedc-filter-clear-button")}
|
className={cn(COMMON_OPERATION_BUTTON_CLASSNAME, "min-w-[14rem]")}
|
||||||
onClick={filter.saveView}
|
onClick={filter.saveView}
|
||||||
data-ph-element={trackerElements?.saveView}
|
data-ph-element={trackerElements?.saveView}
|
||||||
>
|
>
|
||||||
{filter.saveViewOptions?.label ?? "Сохранить вид"}
|
{filter.saveViewOptions?.label ?? "Сохранить вид"}
|
||||||
</Button>
|
</DockActionButton>
|
||||||
</ElementTransition>
|
</ElementTransition>
|
||||||
<ElementTransition show={filter.canUpdateView}>
|
<ElementTransition show={filter.canUpdateView}>
|
||||||
<Button
|
<DockActionButton
|
||||||
variant="secondary"
|
variant="secondary"
|
||||||
className={cn(COMMON_OPERATION_BUTTON_CLASSNAME, "nodedc-filter-clear-button")}
|
className={COMMON_OPERATION_BUTTON_CLASSNAME}
|
||||||
onClick={handleUpdate}
|
onClick={handleUpdate}
|
||||||
loading={isUpdating}
|
|
||||||
disabled={isUpdating}
|
disabled={isUpdating}
|
||||||
data-ph-element={trackerElements?.updateView}
|
data-ph-element={trackerElements?.updateView}
|
||||||
>
|
>
|
||||||
{isUpdating ? "Подтверждение..." : (filter.updateViewOptions?.label ?? "Обновить вид")}
|
{isUpdating ? "Подтверждение..." : (filter.updateViewOptions?.label ?? "Обновить вид")}
|
||||||
</Button>
|
</DockActionButton>
|
||||||
</ElementTransition>
|
</ElementTransition>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
|
||||||
const mainContent = (
|
const mainContent = (
|
||||||
<div className="nodedc-filter-row-shell flex w-full items-start gap-2 px-3 py-2">
|
<div className="nodedc-filter-row-shell flex w-full items-center gap-2 px-3 py-2">
|
||||||
<div className="flex w-full flex-wrap items-center gap-2">{leftContent}</div>
|
<div className="flex w-full flex-wrap items-center gap-2">{leftContent}</div>
|
||||||
<div
|
<div
|
||||||
className={cn("flex items-center gap-2", {
|
className={cn("flex items-center gap-2", {
|
||||||
|
|
@ -139,7 +139,11 @@ export const FiltersRow = observer(function FiltersRow<K extends TFilterProperty
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
||||||
const ModalVariant = <div className="flex min-h-11 w-full flex-wrap items-center gap-2 p-2">{mainContent}</div>;
|
const ModalVariant = (
|
||||||
|
<div className="nodedc-modal-field flex min-h-11 w-full flex-wrap items-center gap-2 rounded-[1.5rem] p-2">
|
||||||
|
{mainContent}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
|
||||||
const HeaderVariant = (
|
const HeaderVariant = (
|
||||||
<Header variant={EHeaderVariant.TERNARY} className="min-h-11 bg-transparent !px-0">
|
<Header variant={EHeaderVariant.TERNARY} className="min-h-11 bg-transparent !px-0">
|
||||||
|
|
@ -160,7 +164,35 @@ export const FiltersRow = observer(function FiltersRow<K extends TFilterProperty
|
||||||
return <RowTransition show={filter.isVisible}>{variant === "modal" ? ModalVariant : HeaderVariant}</RowTransition>;
|
return <RowTransition show={filter.isVisible}>{variant === "modal" ? ModalVariant : HeaderVariant}</RowTransition>;
|
||||||
});
|
});
|
||||||
|
|
||||||
const COMMON_OPERATION_BUTTON_CLASSNAME = "min-h-9 px-4 py-1";
|
const COMMON_OPERATION_BUTTON_CLASSNAME = "px-6 py-0 text-[14px] font-medium";
|
||||||
|
|
||||||
|
type TDockActionButtonProps = React.ButtonHTMLAttributes<HTMLButtonElement> & {
|
||||||
|
children: React.ReactNode;
|
||||||
|
variant: "primary" | "secondary";
|
||||||
|
};
|
||||||
|
|
||||||
|
function DockActionButton(props: TDockActionButtonProps) {
|
||||||
|
const { children, className, disabled, variant, type = "button", ...buttonProps } = props;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<button
|
||||||
|
type={type}
|
||||||
|
disabled={disabled}
|
||||||
|
className={cn(
|
||||||
|
"inline-flex items-center justify-center whitespace-nowrap leading-none transition-colors",
|
||||||
|
{
|
||||||
|
"nodedc-bottom-dock-primary-button": variant === "primary",
|
||||||
|
"nodedc-bottom-dock-secondary-button": variant === "secondary",
|
||||||
|
"cursor-not-allowed opacity-60": disabled,
|
||||||
|
},
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
{...buttonProps}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
</button>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
type TElementTransitionProps = {
|
type TElementTransitionProps = {
|
||||||
children: React.ReactNode;
|
children: React.ReactNode;
|
||||||
|
|
|
||||||
|
|
@ -43,7 +43,7 @@ export const StickiesInfinite = observer(function StickiesInfinite() {
|
||||||
useIntersectionObserver(containerRef, shouldObserve ? elementRef : null, handleLoadMore);
|
useIntersectionObserver(containerRef, shouldObserve ? elementRef : null, handleLoadMore);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ContentWrapper ref={containerRef} className="space-y-4">
|
<ContentWrapper ref={containerRef} className="space-y-4 px-1">
|
||||||
<StickiesLayout
|
<StickiesLayout
|
||||||
workspaceSlug={workspaceSlug.toString()}
|
workspaceSlug={workspaceSlug.toString()}
|
||||||
intersectionElement={
|
intersectionElement={
|
||||||
|
|
@ -54,7 +54,7 @@ export const StickiesInfinite = observer(function StickiesInfinite() {
|
||||||
ref={setElementRef}
|
ref={setElementRef}
|
||||||
id="intersection-element"
|
id="intersection-element"
|
||||||
>
|
>
|
||||||
<div className="flex min-h-[300px] w-full rounded-sm">
|
<div className="nodedc-workspace-list-row flex min-h-[300px] w-full rounded-[1.35rem]">
|
||||||
<Loader className="h-full w-full">
|
<Loader className="h-full w-full">
|
||||||
<Loader.Item height="100%" width="100%" />
|
<Loader.Item height="100%" width="100%" />
|
||||||
</Loader>
|
</Loader>
|
||||||
|
|
|
||||||
|
|
@ -22,7 +22,7 @@ import type {
|
||||||
} from "@plane/types";
|
} from "@plane/types";
|
||||||
import { EViewAccess, EIssuesStoreType } from "@plane/types";
|
import { EViewAccess, EIssuesStoreType } from "@plane/types";
|
||||||
import { Input, TextArea } from "@plane/ui";
|
import { Input, TextArea } from "@plane/ui";
|
||||||
import { getComputedDisplayFilters, getComputedDisplayProperties, getTabIndex } from "@plane/utils";
|
import { cn, getComputedDisplayFilters, getComputedDisplayProperties, getTabIndex } from "@plane/utils";
|
||||||
// components
|
// components
|
||||||
import { DisplayFiltersSelection, FiltersDropdown } from "@/components/issues/issue-layouts/filters";
|
import { DisplayFiltersSelection, FiltersDropdown } from "@/components/issues/issue-layouts/filters";
|
||||||
import { WorkItemFiltersRow } from "@/components/work-item-filters/filters-row";
|
import { WorkItemFiltersRow } from "@/components/work-item-filters/filters-row";
|
||||||
|
|
@ -88,6 +88,7 @@ export const ProjectViewForm = observer(function ProjectViewForm(props: Props) {
|
||||||
kanbanFilters: undefined,
|
kanbanFilters: undefined,
|
||||||
};
|
};
|
||||||
const { getIndex } = getTabIndex(ETabIndices.PROJECT_VIEW, isMobile);
|
const { getIndex } = getTabIndex(ETabIndices.PROJECT_VIEW, isMobile);
|
||||||
|
const viewPropertyButtonClassName = "nodedc-work-item-property-button !min-h-7 !px-3 !py-0 text-12 font-medium";
|
||||||
|
|
||||||
const handleCreateUpdateView = async (formData: IProjectView) => {
|
const handleCreateUpdateView = async (formData: IProjectView) => {
|
||||||
await handleFormSubmit({
|
await handleFormSubmit({
|
||||||
|
|
@ -106,8 +107,8 @@ export const ProjectViewForm = observer(function ProjectViewForm(props: Props) {
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<form onSubmit={handleSubmit(handleCreateUpdateView)}>
|
<form onSubmit={handleSubmit(handleCreateUpdateView)} className="flex flex-col">
|
||||||
<div className="space-y-5 p-5">
|
<div className="space-y-5 px-6 py-6">
|
||||||
<h3 className="text-18 font-medium text-secondary">{data ? t("view.update.label") : t("view.create.label")}</h3>
|
<h3 className="text-18 font-medium text-secondary">{data ? t("view.update.label") : t("view.create.label")}</h3>
|
||||||
<div className="space-y-3">
|
<div className="space-y-3">
|
||||||
<div className="flex w-full items-start gap-2">
|
<div className="flex w-full items-start gap-2">
|
||||||
|
|
@ -118,7 +119,7 @@ export const ProjectViewForm = observer(function ProjectViewForm(props: Props) {
|
||||||
className="flex-shrink0 flex items-center justify-center"
|
className="flex-shrink0 flex items-center justify-center"
|
||||||
buttonClassName="flex items-center justify-center"
|
buttonClassName="flex items-center justify-center"
|
||||||
label={
|
label={
|
||||||
<span className="grid h-9 w-9 place-items-center rounded-md bg-surface-2">
|
<span className="grid h-10 w-10 place-items-center rounded-[1.1rem] bg-white/6">
|
||||||
<>
|
<>
|
||||||
{logoValue?.in_use ? (
|
{logoValue?.in_use ? (
|
||||||
<Logo logo={logoValue} size={18} type="lucide" />
|
<Logo logo={logoValue} size={18} type="lucide" />
|
||||||
|
|
@ -171,7 +172,7 @@ export const ProjectViewForm = observer(function ProjectViewForm(props: Props) {
|
||||||
onChange={onChange}
|
onChange={onChange}
|
||||||
hasError={Boolean(errors.name)}
|
hasError={Boolean(errors.name)}
|
||||||
placeholder={t("common.title")}
|
placeholder={t("common.title")}
|
||||||
className="w-full text-14"
|
className="nodedc-modal-input w-full !px-4 !py-3 text-13"
|
||||||
tabIndex={getIndex("name")}
|
tabIndex={getIndex("name")}
|
||||||
autoFocus
|
autoFocus
|
||||||
/>
|
/>
|
||||||
|
|
@ -189,7 +190,7 @@ export const ProjectViewForm = observer(function ProjectViewForm(props: Props) {
|
||||||
id="description"
|
id="description"
|
||||||
name="description"
|
name="description"
|
||||||
placeholder={t("common.description")}
|
placeholder={t("common.description")}
|
||||||
className="min-h-24 w-full resize-none text-14"
|
className="nodedc-modal-input min-h-[9.5rem] w-full resize-none !rounded-[1.5rem] !px-4 !py-4 text-13"
|
||||||
hasError={Boolean(errors?.description)}
|
hasError={Boolean(errors?.description)}
|
||||||
value={value}
|
value={value}
|
||||||
onChange={onChange}
|
onChange={onChange}
|
||||||
|
|
@ -198,7 +199,7 @@ export const ProjectViewForm = observer(function ProjectViewForm(props: Props) {
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex gap-2">
|
<div className="nodedc-work-item-properties-row">
|
||||||
<AccessController control={control} />
|
<AccessController control={control} />
|
||||||
<Controller
|
<Controller
|
||||||
control={control}
|
control={control}
|
||||||
|
|
@ -214,13 +215,26 @@ export const ProjectViewForm = observer(function ProjectViewForm(props: Props) {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
value={displayFilters.layout}
|
value={displayFilters.layout}
|
||||||
|
buttonContainerClassName={viewPropertyButtonClassName}
|
||||||
/>
|
/>
|
||||||
{/* display filters dropdown */}
|
{/* display filters dropdown */}
|
||||||
<Controller
|
<Controller
|
||||||
control={control}
|
control={control}
|
||||||
name="display_properties"
|
name="display_properties"
|
||||||
render={({ field: { onChange: onDisplayPropertiesChange, value: displayProperties } }) => (
|
render={({ field: { onChange: onDisplayPropertiesChange, value: displayProperties } }) => (
|
||||||
<FiltersDropdown title={t("common.display")}>
|
<FiltersDropdown
|
||||||
|
title={t("common.display")}
|
||||||
|
menuButtonWrapperClassName="h-auto w-auto border-none bg-transparent p-0 hover:bg-transparent"
|
||||||
|
menuButton={({ open }) => (
|
||||||
|
<div
|
||||||
|
className={cn(viewPropertyButtonClassName, "flex items-center gap-2", {
|
||||||
|
"!bg-white/8": open,
|
||||||
|
})}
|
||||||
|
>
|
||||||
|
<span>{t("common.display")}</span>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
>
|
||||||
<DisplayFiltersSelection
|
<DisplayFiltersSelection
|
||||||
layoutDisplayFiltersOptions={
|
layoutDisplayFiltersOptions={
|
||||||
ISSUE_DISPLAY_FILTERS_BY_PAGE.issues.layoutOptions[displayFilters.layout]
|
ISSUE_DISPLAY_FILTERS_BY_PAGE.issues.layoutOptions[displayFilters.layout]
|
||||||
|
|
@ -279,11 +293,24 @@ export const ProjectViewForm = observer(function ProjectViewForm(props: Props) {
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex items-center justify-end gap-2 border-t-[0.5px] border-subtle px-5 py-4">
|
<div className="flex items-center justify-end gap-3 border-t-[0.5px] border-white/6 px-6 py-4">
|
||||||
<Button variant="secondary" size="lg" onClick={handleClose} tabIndex={getIndex("cancel")}>
|
<Button
|
||||||
|
variant="secondary"
|
||||||
|
size="lg"
|
||||||
|
onClick={handleClose}
|
||||||
|
tabIndex={getIndex("cancel")}
|
||||||
|
className="nodedc-modal-secondary-button min-w-[8.25rem]"
|
||||||
|
>
|
||||||
{t("common.cancel")}
|
{t("common.cancel")}
|
||||||
</Button>
|
</Button>
|
||||||
<Button variant="primary" size="lg" type="submit" tabIndex={getIndex("submit")} loading={isSubmitting}>
|
<Button
|
||||||
|
variant="primary"
|
||||||
|
size="lg"
|
||||||
|
type="submit"
|
||||||
|
tabIndex={getIndex("submit")}
|
||||||
|
loading={isSubmitting}
|
||||||
|
className="nodedc-modal-primary-button min-w-[8.25rem]"
|
||||||
|
>
|
||||||
{data
|
{data
|
||||||
? isSubmitting
|
? isSubmitting
|
||||||
? t("common.updating")
|
? t("common.updating")
|
||||||
|
|
|
||||||
|
|
@ -88,7 +88,12 @@ export const CreateUpdateProjectViewModal = observer(function CreateUpdateProjec
|
||||||
});
|
});
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ModalCore isOpen={isOpen} position={EModalPosition.TOP} width={EModalWidth.XXL}>
|
<ModalCore
|
||||||
|
isOpen={isOpen}
|
||||||
|
position={EModalPosition.CENTER}
|
||||||
|
width={EModalWidth.XXXXL}
|
||||||
|
className="overflow-hidden rounded-[1.75rem] transition-[width] ease-linear"
|
||||||
|
>
|
||||||
<ProjectViewForm
|
<ProjectViewForm
|
||||||
data={data}
|
data={data}
|
||||||
handleClose={handleClose}
|
handleClose={handleClose}
|
||||||
|
|
|
||||||
|
|
@ -20,13 +20,16 @@ export const GlobalDefaultViewListItem = observer(function GlobalDefaultViewList
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="group border-b border-subtle hover:bg-surface-2">
|
<div className="nodedc-workspace-list-row group">
|
||||||
<Link href={`/${workspaceSlug}/workspace-views/${view.key}`}>
|
<Link href={`/${workspaceSlug}/workspace-views/${view.key}`}>
|
||||||
<div className="relative flex h-[52px] w-full items-center justify-between rounded-sm px-5 py-4">
|
<div className="relative flex min-h-[4.25rem] w-full items-center justify-between px-5 py-4">
|
||||||
<div className="flex w-full items-center justify-between">
|
<div className="flex w-full items-center justify-between">
|
||||||
<div className="flex items-center gap-4">
|
<div className="flex items-center gap-4">
|
||||||
<div className="flex flex-col">
|
<div className="flex flex-col">
|
||||||
<p className="truncate text-13 leading-4 font-medium">{truncateText(t(view.i18n_label), 75)}</p>
|
<p className="truncate text-13 leading-4 font-medium text-primary">
|
||||||
|
{truncateText(t(view.i18n_label), 75)}
|
||||||
|
</p>
|
||||||
|
<p className="text-11 text-secondary">Default workspace view</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -13,7 +13,7 @@ import { Button } from "@plane/propel/button";
|
||||||
import type { IIssueDisplayFilterOptions, IIssueDisplayProperties, IWorkspaceView, IIssueFilters } from "@plane/types";
|
import type { IIssueDisplayFilterOptions, IIssueDisplayProperties, IWorkspaceView, IIssueFilters } from "@plane/types";
|
||||||
import { EViewAccess, EIssueLayoutTypes, EIssuesStoreType } from "@plane/types";
|
import { EViewAccess, EIssueLayoutTypes, EIssuesStoreType } from "@plane/types";
|
||||||
import { Input, TextArea } from "@plane/ui";
|
import { Input, TextArea } from "@plane/ui";
|
||||||
import { getComputedDisplayFilters, getComputedDisplayProperties } from "@plane/utils";
|
import { cn, getComputedDisplayFilters, getComputedDisplayProperties } from "@plane/utils";
|
||||||
// components
|
// components
|
||||||
import { DisplayFiltersSelection, FiltersDropdown } from "@/components/issues/issue-layouts/filters";
|
import { DisplayFiltersSelection, FiltersDropdown } from "@/components/issues/issue-layouts/filters";
|
||||||
import { WorkspaceLevelWorkItemFiltersHOC } from "@/components/work-item-filters/filters-hoc/workspace-level";
|
import { WorkspaceLevelWorkItemFiltersHOC } from "@/components/work-item-filters/filters-hoc/workspace-level";
|
||||||
|
|
@ -66,6 +66,7 @@ export const WorkspaceViewForm = observer(function WorkspaceViewForm(props: Prop
|
||||||
displayProperties: getValues("display_properties"),
|
displayProperties: getValues("display_properties"),
|
||||||
kanbanFilters: undefined,
|
kanbanFilters: undefined,
|
||||||
};
|
};
|
||||||
|
const viewPropertyButtonClassName = "nodedc-work-item-property-button !min-h-7 !px-3 !py-0 text-12 font-medium";
|
||||||
|
|
||||||
const handleCreateUpdateView = async (formData: Partial<IWorkspaceView>) => {
|
const handleCreateUpdateView = async (formData: Partial<IWorkspaceView>) => {
|
||||||
await handleFormSubmit(formData);
|
await handleFormSubmit(formData);
|
||||||
|
|
@ -75,8 +76,8 @@ export const WorkspaceViewForm = observer(function WorkspaceViewForm(props: Prop
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<form onSubmit={handleSubmit(handleCreateUpdateView)}>
|
<form onSubmit={handleSubmit(handleCreateUpdateView)} className="flex flex-col">
|
||||||
<div className="space-y-5 p-5">
|
<div className="space-y-5 px-6 py-6">
|
||||||
<h3 className="text-18 font-medium text-secondary">{data ? t("view.update.label") : t("view.create.label")}</h3>
|
<h3 className="text-18 font-medium text-secondary">{data ? t("view.update.label") : t("view.create.label")}</h3>
|
||||||
<div className="space-y-3">
|
<div className="space-y-3">
|
||||||
<div className="space-y-1">
|
<div className="space-y-1">
|
||||||
|
|
@ -100,7 +101,7 @@ export const WorkspaceViewForm = observer(function WorkspaceViewForm(props: Prop
|
||||||
ref={ref}
|
ref={ref}
|
||||||
hasError={Boolean(errors.name)}
|
hasError={Boolean(errors.name)}
|
||||||
placeholder={t("common.title")}
|
placeholder={t("common.title")}
|
||||||
className="w-full text-14"
|
className="nodedc-modal-input w-full !px-4 !py-3 text-13"
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
|
|
@ -117,13 +118,13 @@ export const WorkspaceViewForm = observer(function WorkspaceViewForm(props: Prop
|
||||||
value={value}
|
value={value}
|
||||||
placeholder={t("common.description")}
|
placeholder={t("common.description")}
|
||||||
onChange={onChange}
|
onChange={onChange}
|
||||||
className="min-h-24 w-full resize-none text-14"
|
className="nodedc-modal-input min-h-[9.5rem] w-full resize-none !rounded-[1.5rem] !px-4 !py-4 text-13"
|
||||||
hasError={Boolean(errors?.description)}
|
hasError={Boolean(errors?.description)}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex gap-2">
|
<div className="nodedc-work-item-properties-row">
|
||||||
<AccessController control={control} />
|
<AccessController control={control} />
|
||||||
{/* display filters dropdown */}
|
{/* display filters dropdown */}
|
||||||
<Controller
|
<Controller
|
||||||
|
|
@ -134,7 +135,19 @@ export const WorkspaceViewForm = observer(function WorkspaceViewForm(props: Prop
|
||||||
control={control}
|
control={control}
|
||||||
name="display_properties"
|
name="display_properties"
|
||||||
render={({ field: { onChange: onDisplayPropertiesChange, value: displayProperties } }) => (
|
render={({ field: { onChange: onDisplayPropertiesChange, value: displayProperties } }) => (
|
||||||
<FiltersDropdown title={t("common.display")}>
|
<FiltersDropdown
|
||||||
|
title={t("common.display")}
|
||||||
|
menuButtonWrapperClassName="h-auto w-auto border-none bg-transparent p-0 hover:bg-transparent"
|
||||||
|
menuButton={({ open }) => (
|
||||||
|
<div
|
||||||
|
className={cn(viewPropertyButtonClassName, "flex items-center gap-2", {
|
||||||
|
"!bg-white/8": open,
|
||||||
|
})}
|
||||||
|
>
|
||||||
|
<span>{t("common.display")}</span>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
>
|
||||||
<DisplayFiltersSelection
|
<DisplayFiltersSelection
|
||||||
layoutDisplayFiltersOptions={ISSUE_DISPLAY_FILTERS_BY_PAGE.my_issues.layoutOptions.spreadsheet}
|
layoutDisplayFiltersOptions={ISSUE_DISPLAY_FILTERS_BY_PAGE.my_issues.layoutOptions.spreadsheet}
|
||||||
displayFilters={displayFilters ?? {}}
|
displayFilters={displayFilters ?? {}}
|
||||||
|
|
@ -185,11 +198,16 @@ export const WorkspaceViewForm = observer(function WorkspaceViewForm(props: Prop
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex items-center justify-end gap-2 border-t-[0.5px] border-subtle px-5 py-4">
|
<div className="flex items-center justify-end gap-3 border-t-[0.5px] border-white/6 px-6 py-4">
|
||||||
<Button variant="secondary" onClick={handleClose}>
|
<Button variant="secondary" onClick={handleClose} className="nodedc-modal-secondary-button min-w-[8.25rem]">
|
||||||
{t("common.cancel")}
|
{t("common.cancel")}
|
||||||
</Button>
|
</Button>
|
||||||
<Button variant="primary" type="submit" loading={isSubmitting}>
|
<Button
|
||||||
|
variant="primary"
|
||||||
|
type="submit"
|
||||||
|
loading={isSubmitting}
|
||||||
|
className="nodedc-modal-primary-button min-w-[8.25rem]"
|
||||||
|
>
|
||||||
{data
|
{data
|
||||||
? isSubmitting
|
? isSubmitting
|
||||||
? t("common.updating")
|
? t("common.updating")
|
||||||
|
|
|
||||||
|
|
@ -104,7 +104,13 @@ export const CreateUpdateWorkspaceViewModal = observer(function CreateUpdateWork
|
||||||
|
|
||||||
if (!workspaceSlug) return null;
|
if (!workspaceSlug) return null;
|
||||||
return (
|
return (
|
||||||
<ModalCore isOpen={isOpen} handleClose={handleClose} position={EModalPosition.TOP} width={EModalWidth.XXL}>
|
<ModalCore
|
||||||
|
isOpen={isOpen}
|
||||||
|
handleClose={handleClose}
|
||||||
|
position={EModalPosition.CENTER}
|
||||||
|
width={EModalWidth.XXXXL}
|
||||||
|
className="overflow-hidden rounded-[1.75rem] transition-[width] ease-linear"
|
||||||
|
>
|
||||||
<WorkspaceViewForm
|
<WorkspaceViewForm
|
||||||
handleFormSubmit={handleFormSubmit}
|
handleFormSubmit={handleFormSubmit}
|
||||||
handleClose={handleClose}
|
handleClose={handleClose}
|
||||||
|
|
|
||||||
|
|
@ -60,21 +60,25 @@ export const GlobalViewListItem = observer(function GlobalViewListItem(props: Pr
|
||||||
<>
|
<>
|
||||||
<CreateUpdateWorkspaceViewModal data={view} isOpen={updateViewModal} onClose={() => setUpdateViewModal(false)} />
|
<CreateUpdateWorkspaceViewModal data={view} isOpen={updateViewModal} onClose={() => setUpdateViewModal(false)} />
|
||||||
<DeleteGlobalViewModal data={view} isOpen={deleteViewModal} onClose={() => setDeleteViewModal(false)} />
|
<DeleteGlobalViewModal data={view} isOpen={deleteViewModal} onClose={() => setDeleteViewModal(false)} />
|
||||||
<div className="group border-b border-subtle hover:bg-surface-2">
|
<div className="nodedc-workspace-list-row group">
|
||||||
<Link href={`/${workspaceSlug}/workspace-views/${view.id}`}>
|
<Link href={`/${workspaceSlug}/workspace-views/${view.id}`}>
|
||||||
<div className="relative flex h-[52px] w-full items-center justify-between rounded-sm p-4">
|
<div className="relative flex min-h-[4.5rem] w-full items-center justify-between px-4 py-4">
|
||||||
<div className="flex w-full items-center justify-between">
|
<div className="flex w-full items-center justify-between">
|
||||||
<div className="flex items-center gap-4">
|
<div className="flex items-center gap-4">
|
||||||
<div className="flex flex-col">
|
<div className="flex flex-col">
|
||||||
<p className="truncate text-13 leading-4 font-medium">{truncateText(view.name, 75)}</p>
|
<p className="truncate text-13 leading-4 font-medium text-primary">{truncateText(view.name, 75)}</p>
|
||||||
{view?.description && <p className="text-11 text-secondary">{view.description}</p>}
|
{view?.description ? (
|
||||||
|
<p className="max-w-[38rem] truncate text-11 text-secondary">{view.description}</p>
|
||||||
|
) : (
|
||||||
|
<p className="text-11 text-tertiary">Custom workspace view</p>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="ml-2 flex flex-shrink-0">
|
<div className="ml-2 flex flex-shrink-0">
|
||||||
<div className="flex items-center gap-4">
|
<div className="flex items-center gap-4">
|
||||||
<ActionDropdown
|
<ActionDropdown
|
||||||
placement="bottom-end"
|
placement="bottom-end"
|
||||||
buttonClassName="grid size-7 place-items-center rounded-sm text-secondary transition-colors hover:bg-layer-transparent-hover"
|
buttonClassName="grid size-8 place-items-center rounded-full bg-white/5 text-secondary transition-colors hover:bg-white/10"
|
||||||
items={menuItems}
|
items={menuItems}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -530,6 +530,7 @@
|
||||||
outline: none !important;
|
outline: none !important;
|
||||||
box-shadow: none !important;
|
box-shadow: none !important;
|
||||||
border-radius: 999px !important;
|
border-radius: 999px !important;
|
||||||
|
height: 2.5rem !important;
|
||||||
min-height: 2.5rem;
|
min-height: 2.5rem;
|
||||||
padding-inline: 1.35rem;
|
padding-inline: 1.35rem;
|
||||||
background: rgba(18, 18, 22, 0.94) !important;
|
background: rgba(18, 18, 22, 0.94) !important;
|
||||||
|
|
@ -558,6 +559,7 @@
|
||||||
outline: none !important;
|
outline: none !important;
|
||||||
box-shadow: none !important;
|
box-shadow: none !important;
|
||||||
border-radius: 999px !important;
|
border-radius: 999px !important;
|
||||||
|
height: 2.5rem !important;
|
||||||
min-height: 2.5rem;
|
min-height: 2.5rem;
|
||||||
padding-inline: 1.55rem;
|
padding-inline: 1.55rem;
|
||||||
background: rgb(var(--nodedc-accent-rgb)) !important;
|
background: rgb(var(--nodedc-accent-rgb)) !important;
|
||||||
|
|
@ -573,6 +575,39 @@
|
||||||
background: color-mix(in srgb, rgb(var(--nodedc-accent-rgb)) 82%, white) !important;
|
background: color-mix(in srgb, rgb(var(--nodedc-accent-rgb)) 82%, white) !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.nodedc-bottom-dock-secondary-button {
|
||||||
|
height: 3rem !important;
|
||||||
|
min-height: 3rem !important;
|
||||||
|
border: 0 !important;
|
||||||
|
outline: none !important;
|
||||||
|
box-shadow: none !important;
|
||||||
|
border-radius: 1.2rem !important;
|
||||||
|
background: rgba(255, 255, 255, 0.06) !important;
|
||||||
|
color: var(--text-color-primary) !important;
|
||||||
|
padding-inline: 1.35rem !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nodedc-bottom-dock-secondary-button:hover {
|
||||||
|
background: rgba(255, 255, 255, 0.1) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nodedc-bottom-dock-primary-button {
|
||||||
|
height: 3rem !important;
|
||||||
|
min-height: 3rem !important;
|
||||||
|
border: 0 !important;
|
||||||
|
outline: none !important;
|
||||||
|
box-shadow: none !important;
|
||||||
|
border-radius: 1.2rem !important;
|
||||||
|
background: rgb(var(--nodedc-card-active-rgb)) !important;
|
||||||
|
color: rgb(var(--nodedc-on-card-active-rgb)) !important;
|
||||||
|
padding-inline: 1.6rem !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nodedc-bottom-dock-primary-button:hover {
|
||||||
|
background: color-mix(in srgb, rgb(var(--nodedc-card-active-rgb)) 82%, white) !important;
|
||||||
|
color: rgb(var(--nodedc-on-card-active-rgb)) !important;
|
||||||
|
}
|
||||||
|
|
||||||
.nodedc-modal-secondary-button {
|
.nodedc-modal-secondary-button {
|
||||||
min-height: 2.75rem;
|
min-height: 2.75rem;
|
||||||
border: 0 !important;
|
border: 0 !important;
|
||||||
|
|
@ -924,6 +959,36 @@
|
||||||
background: rgba(255, 255, 255, 0.08) !important;
|
background: rgba(255, 255, 255, 0.08) !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.nodedc-spreadsheet-cell-button {
|
||||||
|
height: 100% !important;
|
||||||
|
min-height: 2.75rem !important;
|
||||||
|
width: 100% !important;
|
||||||
|
align-items: center !important;
|
||||||
|
justify-content: flex-start !important;
|
||||||
|
border: 0 !important;
|
||||||
|
outline: none !important;
|
||||||
|
box-shadow: none !important;
|
||||||
|
border-radius: 0 !important;
|
||||||
|
background: transparent !important;
|
||||||
|
color: var(--text-color-primary) !important;
|
||||||
|
padding-inline: 1rem !important;
|
||||||
|
font-size: 0.8125rem !important;
|
||||||
|
line-height: 1rem !important;
|
||||||
|
transition: background 160ms ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nodedc-spreadsheet-cell-button:hover {
|
||||||
|
background: rgba(255, 255, 255, 0.035) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.selected-issue-row .nodedc-spreadsheet-cell-button {
|
||||||
|
background: color-mix(in srgb, rgb(var(--nodedc-accent-rgb)) 8%, transparent) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.selected-issue-row .nodedc-spreadsheet-cell-button:hover {
|
||||||
|
background: color-mix(in srgb, rgb(var(--nodedc-accent-rgb)) 14%, transparent) !important;
|
||||||
|
}
|
||||||
|
|
||||||
.nodedc-calendar-shell {
|
.nodedc-calendar-shell {
|
||||||
@apply rounded-[1.1rem] border-0 bg-transparent p-1 shadow-none outline-none;
|
@apply rounded-[1.1rem] border-0 bg-transparent p-1 shadow-none outline-none;
|
||||||
}
|
}
|
||||||
|
|
@ -1304,6 +1369,76 @@
|
||||||
color: rgb(var(--nodedc-on-card-active-rgb)) !important;
|
color: rgb(var(--nodedc-on-card-active-rgb)) !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.nodedc-workspace-page-shell {
|
||||||
|
width: 100%;
|
||||||
|
max-width: 104rem;
|
||||||
|
margin: 0 auto;
|
||||||
|
padding: 1rem 1rem 1.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (min-width: 768px) {
|
||||||
|
.nodedc-workspace-page-shell {
|
||||||
|
padding: 1.25rem 1.25rem 1.75rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.nodedc-workspace-toolbar {
|
||||||
|
border: 0 !important;
|
||||||
|
outline: none !important;
|
||||||
|
border-radius: 1.5rem !important;
|
||||||
|
background:
|
||||||
|
linear-gradient(180deg, rgba(255, 255, 255, 0.036) 0%, rgba(255, 255, 255, 0.016) 100%),
|
||||||
|
rgba(8, 8, 11, 0.76) !important;
|
||||||
|
box-shadow:
|
||||||
|
0 20px 52px rgba(0, 0, 0, 0.22),
|
||||||
|
inset 0 1px 0 rgba(255, 255, 255, 0.025) !important;
|
||||||
|
-webkit-backdrop-filter: blur(28px);
|
||||||
|
backdrop-filter: blur(28px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.nodedc-workspace-list-row {
|
||||||
|
border: 0 !important;
|
||||||
|
outline: none !important;
|
||||||
|
overflow: visible !important;
|
||||||
|
isolation: isolate;
|
||||||
|
border-radius: 1.35rem !important;
|
||||||
|
background:
|
||||||
|
linear-gradient(180deg, rgba(255, 255, 255, 0.032) 0%, rgba(255, 255, 255, 0.014) 100%),
|
||||||
|
rgba(255, 255, 255, 0.026) !important;
|
||||||
|
box-shadow:
|
||||||
|
0 12px 28px rgba(0, 0, 0, 0.14),
|
||||||
|
inset 0 1px 0 rgba(255, 255, 255, 0.016) !important;
|
||||||
|
-webkit-backdrop-filter: blur(18px);
|
||||||
|
backdrop-filter: blur(18px);
|
||||||
|
transition:
|
||||||
|
background 160ms ease,
|
||||||
|
transform 160ms ease,
|
||||||
|
box-shadow 160ms ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nodedc-workspace-list-row:hover {
|
||||||
|
background:
|
||||||
|
linear-gradient(180deg, rgba(255, 255, 255, 0.044) 0%, rgba(255, 255, 255, 0.018) 100%),
|
||||||
|
rgba(255, 255, 255, 0.036) !important;
|
||||||
|
box-shadow:
|
||||||
|
0 16px 34px rgba(0, 0, 0, 0.18),
|
||||||
|
inset 0 1px 0 rgba(255, 255, 255, 0.024) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nodedc-workspace-stat-card {
|
||||||
|
border: 0 !important;
|
||||||
|
outline: none !important;
|
||||||
|
border-radius: 1.3rem !important;
|
||||||
|
background:
|
||||||
|
linear-gradient(180deg, rgba(255, 255, 255, 0.028) 0%, rgba(255, 255, 255, 0.01) 100%),
|
||||||
|
rgba(255, 255, 255, 0.022) !important;
|
||||||
|
box-shadow:
|
||||||
|
0 14px 32px rgba(0, 0, 0, 0.14),
|
||||||
|
inset 0 1px 0 rgba(255, 255, 255, 0.018) !important;
|
||||||
|
-webkit-backdrop-filter: blur(18px);
|
||||||
|
backdrop-filter: blur(18px);
|
||||||
|
}
|
||||||
|
|
||||||
.nodedc-external-empty-state {
|
.nodedc-external-empty-state {
|
||||||
display: flex;
|
display: flex;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue