UI - МЕЖПРОЕКТНАЯ КОММУНИКАЦИЯ: выправка дизайна views аналитики и рабочих экранов

This commit is contained in:
DCCONSTRUCTIONS
2026-04-22 16:09:30 +03:00
parent 3f6219fc50
commit aa9c278b59
46 changed files with 455 additions and 192 deletions
@@ -16,14 +16,14 @@ type Props = {
};
function AnalyticsSectionWrapper(props: Props) {
const { title, children, className, actions, headerClassName } = props;
const { title, children, className, actions, headerClassName, subtitle } = props;
return (
<div className={className}>
<div className={cn("mb-6 flex items-center gap-2 text-nowrap", headerClassName)}>
<div className={cn("nodedc-external-section rounded-[1.7rem] px-5 py-5 md:px-6 md:py-6", className)}>
<div className={cn("mb-5 flex flex-wrap items-start justify-between gap-3", headerClassName)}>
{title && (
<div className="flex items-center gap-2">
<h1 className={"text-16 font-medium"}>{title}</h1>
{/* {subtitle && <p className="text-16 text-tertiary"> • {subtitle}</p>} */}
<div className="flex flex-col gap-1">
<h1 className="text-16 font-semibold text-primary">{title}</h1>
{subtitle ? <p className="text-12 text-secondary">{subtitle}</p> : null}
</div>
)}
{actions}
@@ -19,8 +19,13 @@ function AnalyticsWrapper(props: Props) {
const { i18nTitle, children, className } = props;
const { t } = useTranslation();
return (
<div className={cn("px-6 py-4", className)}>
<h1 className={"mb-4 text-20 font-bold md:mb-6"}>{t(i18nTitle)}</h1>
<div className={cn("flex flex-col gap-4 pb-5", className)}>
<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}
</div>
);
@@ -20,11 +20,11 @@ function InsightCard(props: InsightCardProps) {
const count = data?.count ?? 0;
return (
<div className="flex flex-col gap-3">
<div className="text-13 text-tertiary">{label}</div>
<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-12 font-medium text-secondary">{label}</div>
{!isLoading ? (
<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>
) : (
<Loader.Item height="50px" width="100%" />
@@ -23,9 +23,11 @@ type Props = {
function CompletionPercentage({ percentage }: { percentage: number }) {
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 (
<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>
</div>
);
@@ -41,9 +43,9 @@ function ActiveProjectItem(props: Props) {
if (!projectDetails) return null;
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 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">
{projectDetails?.logo_props ? (
<Logo logo={projectDetails?.logo_props} size={16} />
@@ -63,7 +63,7 @@ const ProjectInsights = observer(function ProjectInsights() {
<EmptyStateCompact
assetKey="unknown"
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")}
/>
) : (
@@ -95,9 +95,13 @@ const ProjectInsights = observer(function ProjectInsights() {
/>
</Suspense>
)}
<div className="w-full lg:w-2/5">
<div className="text-13 text-tertiary">{t("workspace_analytics.summary_of_projects")}</div>
<div className="mb-3 border-b border-subtle py-2">{t("workspace_analytics.all_projects")}</div>
<div className="nodedc-workspace-list-row flex w-full flex-col gap-3 px-4 py-4 lg:w-2/5">
<div className="text-12 font-medium tracking-[0.16em] text-secondary uppercase">
{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 items-center justify-between text-13 text-tertiary">
<div>{t("workspace_analytics.trend_on_charts")}</div>
@@ -13,9 +13,9 @@ import ProjectInsights from "./project-insights";
function Overview() {
return (
<AnalyticsWrapper i18nTitle="common.overview">
<div className="flex flex-col gap-14">
<div className="flex flex-col gap-5 md:gap-6">
<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 />
<ActiveProjects />
</div>
@@ -6,7 +6,6 @@
import { observer } from "mobx-react";
// plane package imports
import { getButtonStyling } from "@plane/propel/button";
import { Logo } from "@plane/propel/emoji-icon-picker";
import { ChevronDownIcon, ProjectIcon } from "@plane/propel/icons";
import { SearchSelectionDropdown } from "@plane/ui";
@@ -49,8 +48,14 @@ export const ProjectSelect = observer(function ProjectSelect(props: Props) {
onChange={(val: string[]) => onChange(val)}
options={options}
className="border-none p-0"
menuButton={
<div className={cn(getButtonStyling("secondary", "lg"), "gap-2")}>
menuButton={({ open }) => (
<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" />
{value && value.length > 3
? `3+ projects`
@@ -62,7 +67,7 @@ export const ProjectSelect = observer(function ProjectSelect(props: Props) {
: "All projects"}
<ChevronDownIcon className="h-3 w-3" aria-hidden="true" />
</div>
}
)}
menuButtonWrapperClassName="h-auto w-auto border-none bg-transparent p-0 hover:bg-transparent"
multiple
/>
@@ -14,7 +14,7 @@ import WorkItemsInsightTable from "./workitems-insight-table";
function WorkItems() {
return (
<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" />
<CreatedVsResolved />
<CustomizedInsights />
@@ -16,6 +16,7 @@ import { useTranslation } from "@plane/i18n";
// icon
import { CheckIcon, CycleGroupIcon, CycleIcon, SearchIcon } from "@plane/propel/icons";
import type { TCycleGroups } from "@plane/types";
import { cn } from "@plane/utils";
// ui
// store hooks
import { useCycle } from "@/hooks/store/use-cycle";
@@ -126,17 +127,17 @@ export const CycleOptions = observer(function CycleOptions(props: CycleOptionsPr
return (
<Combobox.Options className="fixed z-10" static>
<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}
style={styles.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} />
<Combobox.Input
as="input"
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}
onChange={(e) => setQuery(e.target.value)}
placeholder={t("common.search.label")}
@@ -144,7 +145,7 @@ export const CycleOptions = observer(function CycleOptions(props: CycleOptionsPr
onKeyDown={searchInputKeyDown}
/>
</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.length > 0 ? (
filteredOptions.map((option) => (
@@ -152,9 +153,11 @@ export const CycleOptions = observer(function CycleOptions(props: CycleOptionsPr
key={option.value}
value={option.value}
className={({ active, selected }) =>
`flex w-full cursor-pointer items-center justify-between gap-2 truncate rounded-sm px-1 py-1.5 select-none ${
active ? "bg-layer-transparent-hover" : ""
} ${selected ? "text-primary" : "text-secondary"}`
cn("nodedc-dropdown-option", {
"bg-white/6": active,
"text-primary": selected,
"text-secondary": !selected,
})
}
>
{({ selected }) => (
@@ -166,10 +169,10 @@ export const CycleOptions = observer(function CycleOptions(props: CycleOptionsPr
</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>
@@ -234,17 +234,17 @@ export const EstimateDropdown = observer(function EstimateDropdown(props: Props)
{isOpen && (
<Combobox.Options className="fixed z-10" static>
<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}
style={styles.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} />
<Combobox.Input
as="input"
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}
onChange={(e) => setQuery(e.target.value)}
placeholder={t("common.search.placeholder")}
@@ -252,11 +252,9 @@ export const EstimateDropdown = observer(function EstimateDropdown(props: Props)
onKeyDown={searchInputKeyDown}
/>
</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 ? (
<div
className={`flex w-full cursor-pointer items-center justify-between gap-2 truncate rounded-sm px-1 py-1.5 text-secondary select-none`}
>
<div className="nodedc-dropdown-option cursor-default">
{/* NOTE: This condition renders when estimates are not enabled for the project */}
<div className="flex flex-grow items-center gap-2">
<EstimatePropertyIcon className="h-3 w-3 flex-shrink-0" />
@@ -272,9 +270,9 @@ export const EstimateDropdown = observer(function EstimateDropdown(props: Props)
{({ active, selected }) => (
<div
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-secondary": !selected,
}
@@ -287,10 +285,10 @@ export const EstimateDropdown = observer(function EstimateDropdown(props: Props)
</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}
>
{isOpen && (
<Combobox.Options className="fixed z-10" static>
<Combobox.Options className="fixed z-30" static>
<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}
style={styles.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} />
<Combobox.Input
as="input"
@@ -230,7 +230,7 @@ export const WorkItemStateDropdownBase = observer(function WorkItemStateDropdown
onKeyDown={searchInputKeyDown}
/>
</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.length > 0 ? (
filteredOptions.map((option) => (
@@ -238,11 +238,11 @@ export const WorkItemStateDropdownBase = observer(function WorkItemStateDropdown
key={option.value}
value={option.value}
className={({ active, selected }) =>
cn(
`flex w-full cursor-pointer items-center justify-between gap-2 truncate rounded-[0.9rem] px-2 py-2 select-none outline-none ${
active ? "bg-white/6" : ""
} ${selected ? "text-primary" : "text-secondary"}`
)
cn("nodedc-dropdown-option", {
"bg-white/6": active,
"text-primary": selected,
"text-secondary": !selected,
})
}
>
{({ selected }) => (
@@ -254,10 +254,10 @@ export const WorkItemStateDropdownBase = observer(function WorkItemStateDropdown
</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>
@@ -21,10 +21,11 @@ type TLayoutDropDown = {
onChange: (value: EIssueLayoutTypes) => void;
value: EIssueLayoutTypes;
disabledLayouts?: EIssueLayoutTypes[];
buttonContainerClassName?: string;
};
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
const { t } = useTranslation();
// derived values
@@ -74,7 +75,7 @@ export const LayoutDropDown = observer(function LayoutDropDown(props: TLayoutDro
value={value?.toString()}
keyExtractor={keyExtractor}
options={options}
buttonContainerClassName={cn(getIconButtonStyling("secondary", "lg"), "w-auto px-2")}
buttonContainerClassName={cn(getIconButtonStyling("secondary", "lg"), "w-auto px-2", buttonContainerClassName)}
buttonContent={buttonContent}
renderItem={itemContent}
disableSearch
@@ -115,17 +115,17 @@ export const ModuleOptions = observer(function ModuleOptions(props: Props) {
return (
<Combobox.Options className="fixed z-10" static>
<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}
style={styles.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} />
<Combobox.Input
as="input"
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}
onChange={(e) => setQuery(e.target.value)}
placeholder={t("common.search.label")}
@@ -133,7 +133,7 @@ export const ModuleOptions = observer(function ModuleOptions(props: Props) {
onKeyDown={searchInputKeyDown}
/>
</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.length > 0 ? (
filteredOptions.map((option) => (
@@ -142,9 +142,9 @@ export const ModuleOptions = observer(function ModuleOptions(props: Props) {
value={option.value}
className={({ active, selected }) =>
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-secondary": !selected,
}
@@ -160,10 +160,10 @@ export const ModuleOptions = observer(function ModuleOptions(props: Props) {
</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>
@@ -216,12 +216,12 @@ export const WorkItemStateDropdownBase = observer(function WorkItemStateDropdown
createPortal(
<Combobox.Options data-prevent-outside-click className="fixed z-30" static>
<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}
style={styles.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} />
<Combobox.Input
as="input"
@@ -234,7 +234,7 @@ export const WorkItemStateDropdownBase = observer(function WorkItemStateDropdown
onKeyDown={searchInputKeyDown}
/>
</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.length > 0 ? (
filteredOptions.map((option) => (
@@ -242,11 +242,11 @@ export const WorkItemStateDropdownBase = observer(function WorkItemStateDropdown
key={option.value}
value={option.value}
className={({ active, selected }) =>
cn(
`flex w-full cursor-pointer items-center justify-between gap-2 truncate rounded-[0.9rem] px-2 py-2 select-none outline-none ${
active ? "bg-white/6" : ""
} ${selected ? "text-primary" : "text-secondary"}`
)
cn("nodedc-dropdown-option", {
"bg-white/6": active,
"text-primary": selected,
"text-secondary": !selected,
})
}
>
{({ selected }) => (
@@ -258,10 +258,10 @@ export const WorkItemStateDropdownBase = observer(function WorkItemStateDropdown
</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>
@@ -8,12 +8,12 @@ import { useState } from "react";
import { omit } from "lodash-es";
import { observer } from "mobx-react";
import { useParams } from "next/navigation";
import { Ellipsis } from "lucide-react";
import { MoreHorizontal } from "lucide-react";
// plane imports
import { ARCHIVABLE_STATE_GROUPS, EUserPermissions, EUserPermissionsLevel } from "@plane/constants";
import type { TIssue } from "@plane/types";
import { EIssuesStoreType } from "@plane/types";
import { ActionDropdown, ContextMenu } from "@plane/ui";
import { ActionDropdown, ContextMenu, cn } from "@plane/ui";
// hooks
import { useIssues } from "@/hooks/store/use-issues";
import { useProject } from "@/hooks/store/use-project";
@@ -240,15 +240,17 @@ export const WorkItemDetailQuickActions = observer(function WorkItemDetailQuickA
<ContextMenu parentRef={parentRef} items={CONTEXT_MENU_ITEMS} />
<ActionDropdown
button={
<div
className={
buttonClassName ??
"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"
}
<button
type="button"
className={cn(
"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" />
</div>
<MoreHorizontal className="pointer-events-none h-4 w-4 shrink-0" />
</button>
}
buttonAsChild
items={MENU_ITEMS}
placement={placements}
portalElement={portalElement}
@@ -42,7 +42,7 @@ export const SpreadsheetAssigneeColumn = observer(function SpreadsheetAssigneeCo
buttonVariant={
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"
optionsClassName="z-[9]"
onClose={onClose}
@@ -47,8 +47,8 @@ export const SpreadsheetCycleColumn = observer(function SpreadsheetCycleColumn(p
disabled={disabled}
placeholder="Select cycle"
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"
buttonClassName="relative leading-4 h-4.5 bg-transparent hover:bg-transparent px-0"
buttonContainerClassName="w-full"
buttonClassName="nodedc-spreadsheet-cell-button text-left"
onClose={onClose}
/>
</div>
@@ -52,7 +52,7 @@ export const SpreadsheetDueDateColumn = observer(function SpreadsheetDueDateColu
buttonVariant="transparent-with-text"
buttonContainerClassName="w-full"
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),
}
@@ -31,7 +31,7 @@ export const SpreadsheetEstimateColumn = observer(function SpreadsheetEstimateCo
projectId={issue.project_id ?? undefined}
disabled={disabled}
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"
onClose={onClose}
/>
@@ -35,7 +35,7 @@ export const SpreadsheetLabelColumn = observer(function SpreadsheetLabelColumn(p
defaultOptions={defaultLabelOptions}
onChange={(data) => onChange(issue, { label_ids: data }, { changed_property: "labels", change_details: data })}
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
maxRender={1}
disabled={disabled}
@@ -55,8 +55,8 @@ export const SpreadsheetModuleColumn = observer(function SpreadsheetModuleColumn
disabled={disabled}
placeholder="Select modules"
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"
buttonClassName="relative leading-4 h-4.5 bg-transparent hover:bg-transparent !px-0"
buttonContainerClassName="w-full"
buttonClassName="nodedc-spreadsheet-cell-button text-left"
onClose={onClose}
multiple
showCount
@@ -28,7 +28,7 @@ export const SpreadsheetPriorityColumn = observer(function SpreadsheetPriorityCo
onChange={(data) => onChange(issue, { priority: data }, { changed_property: "priority", change_details: data })}
disabled={disabled}
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"
onClose={onClose}
/>
@@ -44,7 +44,7 @@ export const SpreadsheetStartDateColumn = observer(function SpreadsheetStartDate
placeholder="Start date"
icon={<StartDatePropertyIcon className="h-3 w-3 flex-shrink-0" />}
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"
optionsClassName="z-[9]"
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 })}
disabled={disabled}
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"
onClose={onClose}
showTooltip
@@ -126,7 +126,7 @@ export const DraftIssueBlock = observer(function DraftIssueBlock(props: Props) {
/>
<div
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={() => {
setIssueToEdit(issue);
setCreateUpdateIssueModal(true);
@@ -135,7 +135,7 @@ export const DraftIssueBlock = observer(function DraftIssueBlock(props: Props) {
<Row
ref={issueRef}
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,
"lg:flex-row lg:items-center": !isSidebarCollapsed,
@@ -170,7 +170,7 @@ export const DraftIssueBlock = observer(function DraftIssueBlock(props: Props) {
{/* quick actions */}
<div
className={cn("block rounded-sm border border-strong", {
className={cn("block", {
"md:hidden": isSidebarCollapsed,
"lg:hidden": !isSidebarCollapsed,
})}
@@ -35,7 +35,7 @@ export const WorkspaceDraftEmptyState = observer(function WorkspaceDraftEmptySta
onClose={() => setIsDraftIssueModalOpen(false)}
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
title={t("workspace_empty_state.drafts.title")}
description={t("workspace_empty_state.drafts.description")}
@@ -87,7 +87,7 @@ export const WorkspaceDraftIssuesRoot = observer(function WorkspaceDraftIssuesRo
if (issueIds.length <= 0) return <WorkspaceDraftEmptyState />;
return (
<div className="relative">
<div className="relative flex flex-col gap-3">
<div className="relative">
{issueIds.map((issueId: string) => (
<DraftIssueBlock key={issueId} workspaceSlug={workspaceSlug} issueId={issueId} />
@@ -100,8 +100,8 @@ export const WorkspaceDraftIssuesRoot = observer(function WorkspaceDraftIssuesRo
<WorkspaceDraftIssuesLoader items={1} />
) : (
<div
className={cn("h-11 border-b border-subtle bg-surface-1 p-3 pl-6 text-13 font-medium transition-all", {
"cursor-pointer text-accent-primary underline-offset-2 hover:text-accent-secondary hover:underline":
className={cn("h-11 text-13 font-medium transition-all", {
"nodedc-workspace-toolbar flex cursor-pointer items-center justify-center rounded-[1.25rem] border-0 bg-transparent px-4 py-3 text-primary underline-offset-2 hover:text-primary hover:underline":
paginationInfo?.next_page_results,
})}
onClick={handleNextIssues}
@@ -8,7 +8,7 @@ import { ArrowDownWideNarrow } from "lucide-react";
// plane imports
import { PROJECT_ORDER_BY_OPTIONS } from "@plane/constants";
import { useTranslation } from "@plane/i18n";
import { getButtonStyling } from "@plane/propel/button";
import { ChevronDownIcon } from "@plane/propel/icons";
import type { TProjectOrderByOptions } from "@plane/types";
import { SortingDropdown } from "@/components/common/sorting-dropdown";
@@ -32,13 +32,15 @@ export function ProjectOrderByDropdown(props: Props) {
return (
<SortingDropdown
menuButton={
<div className={`${isMobile ? "flex w-full justify-center" : ""}`}>
<div className={getButtonStyling("secondary", "lg")}>
<div className={isMobile ? "flex w-full justify-center" : ""}>
<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} />
{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>
}
menuButtonWrapperClassName="h-auto w-auto border-none bg-transparent p-0 hover:bg-transparent"
placement="bottom-end"
title={t("common.order_by.label")}
sections={[
@@ -44,7 +44,7 @@ export const ProjectSearch = observer(function ProjectSearch() {
<IconButton
variant="ghost"
size="lg"
className="-mr-1"
className="nodedc-toolbar-icon-button"
onClick={() => {
setIsSearchOpen(true);
inputRef.current?.focus();
@@ -54,9 +54,9 @@ export const ProjectSearch = observer(function ProjectSearch() {
)}
<div
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 && (
<button
type="button"
className="grid place-items-center"
className="grid h-7 w-7 flex-shrink-0 place-items-center rounded-full text-secondary transition-colors hover:bg-white/6 hover:text-primary"
onClick={() => {
updateSearchQuery("");
setIsSearchOpen(false);
@@ -23,6 +23,7 @@ export type TAddFilterButtonProps<P extends TFilterProperty, E extends TExternal
variant?: TButtonVariant;
size?: TButtonSize;
className?: string;
appearance?: "toolbar" | "modal";
defaultOpen?: boolean;
iconConfig?: {
shouldShowIcon: boolean;
@@ -42,6 +43,7 @@ export const AddFilterButton = observer(function AddFilterButton<P extends TFilt
variant = "secondary",
size = "base",
className,
appearance = "toolbar",
label,
iconConfig = { shouldShowIcon: true },
isDisabled = false,
@@ -68,7 +70,11 @@ export const AddFilterButton = observer(function AddFilterButton<P extends TFilt
{...props}
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}
customButton={
@@ -51,7 +51,7 @@ export const DateRangeFilterValueInput = observer(function DateRangeFilterValueI
mergeDates
placeholder={{ from: EMPTY_FILTER_PLACEHOLDER_TEXT }}
buttonVariant="transparent-with-text"
buttonClassName={cn("rounded-none", {
buttonClassName={cn("rounded-none !items-center !py-0", {
[COMMON_FILTER_ITEM_BORDER_CLASSNAME]: !isDisabled,
"text-danger-primary": isIncomplete,
"hover:bg-surface-1": isDisabled,
@@ -33,11 +33,12 @@ export const SingleDateFilterValueInput = observer(function SingleDateFilterValu
const formattedDate = value ? renderFormattedPayloadDate(value) : null;
onChange(formattedDate);
}}
buttonClassName={cn("rounded-none", {
buttonClassName={cn("rounded-none !items-center !py-0", {
[COMMON_FILTER_ITEM_BORDER_CLASSNAME]: !isDisabled,
"text-placeholder": !conditionValue,
"hover:bg-surface-1": isDisabled,
})}
labelClassName="!text-13 !leading-none"
minDate={config.min}
maxDate={config.max}
icon={null}
@@ -10,7 +10,6 @@ import { ListFilterPlus } from "lucide-react";
import { Transition } from "@headlessui/react";
// plane imports
import { useTranslation } from "@plane/i18n";
import { Button } from "@plane/propel/button";
import type { IFilterInstance } from "@plane/shared-state";
import type { TExternalFilter, TFilterProperty } from "@plane/types";
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"]> = {
label: !hasAnyConditions ? t("common.filters") : null,
appearance: "modal",
className: "!min-h-7 !px-3 !py-0 text-12 font-medium",
};
const handleUpdate = useCallback(async () => {
@@ -92,42 +93,41 @@ export const FiltersRow = observer(function FiltersRow<K extends TFilterProperty
const rightContent = !disabledAllOperations && (
<>
<ElementTransition show={filter.canClearFilters}>
<Button
<DockActionButton
variant="secondary"
className={cn(COMMON_OPERATION_BUTTON_CLASSNAME, "nodedc-filter-clear-button")}
className={COMMON_OPERATION_BUTTON_CLASSNAME}
onClick={filter.clearFilters}
data-ph-element={trackerElements?.clearFilter}
>
{t("common.clear")}
</Button>
</DockActionButton>
</ElementTransition>
<ElementTransition show={filter.canSaveView}>
<Button
variant="secondary"
className={cn(COMMON_OPERATION_BUTTON_CLASSNAME, "nodedc-filter-clear-button")}
<DockActionButton
variant="primary"
className={cn(COMMON_OPERATION_BUTTON_CLASSNAME, "min-w-[14rem]")}
onClick={filter.saveView}
data-ph-element={trackerElements?.saveView}
>
{filter.saveViewOptions?.label ?? "Сохранить вид"}
</Button>
</DockActionButton>
</ElementTransition>
<ElementTransition show={filter.canUpdateView}>
<Button
<DockActionButton
variant="secondary"
className={cn(COMMON_OPERATION_BUTTON_CLASSNAME, "nodedc-filter-clear-button")}
className={COMMON_OPERATION_BUTTON_CLASSNAME}
onClick={handleUpdate}
loading={isUpdating}
disabled={isUpdating}
data-ph-element={trackerElements?.updateView}
>
{isUpdating ? "Подтверждение..." : (filter.updateViewOptions?.label ?? "Обновить вид")}
</Button>
</DockActionButton>
</ElementTransition>
</>
);
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={cn("flex items-center gap-2", {
@@ -139,7 +139,11 @@ export const FiltersRow = observer(function FiltersRow<K extends TFilterProperty
</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 = (
<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>;
});
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 = {
children: React.ReactNode;
@@ -43,7 +43,7 @@ export const StickiesInfinite = observer(function StickiesInfinite() {
useIntersectionObserver(containerRef, shouldObserve ? elementRef : null, handleLoadMore);
return (
<ContentWrapper ref={containerRef} className="space-y-4">
<ContentWrapper ref={containerRef} className="space-y-4 px-1">
<StickiesLayout
workspaceSlug={workspaceSlug.toString()}
intersectionElement={
@@ -54,7 +54,7 @@ export const StickiesInfinite = observer(function StickiesInfinite() {
ref={setElementRef}
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.Item height="100%" width="100%" />
</Loader>
@@ -22,7 +22,7 @@ import type {
} from "@plane/types";
import { EViewAccess, EIssuesStoreType } from "@plane/types";
import { Input, TextArea } from "@plane/ui";
import { getComputedDisplayFilters, getComputedDisplayProperties, getTabIndex } from "@plane/utils";
import { cn, getComputedDisplayFilters, getComputedDisplayProperties, getTabIndex } from "@plane/utils";
// components
import { DisplayFiltersSelection, FiltersDropdown } from "@/components/issues/issue-layouts/filters";
import { WorkItemFiltersRow } from "@/components/work-item-filters/filters-row";
@@ -88,6 +88,7 @@ export const ProjectViewForm = observer(function ProjectViewForm(props: Props) {
kanbanFilters: undefined,
};
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) => {
await handleFormSubmit({
@@ -106,8 +107,8 @@ export const ProjectViewForm = observer(function ProjectViewForm(props: Props) {
};
return (
<form onSubmit={handleSubmit(handleCreateUpdateView)}>
<div className="space-y-5 p-5">
<form onSubmit={handleSubmit(handleCreateUpdateView)} className="flex flex-col">
<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>
<div className="space-y-3">
<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"
buttonClassName="flex items-center justify-center"
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 ? (
<Logo logo={logoValue} size={18} type="lucide" />
@@ -171,7 +172,7 @@ export const ProjectViewForm = observer(function ProjectViewForm(props: Props) {
onChange={onChange}
hasError={Boolean(errors.name)}
placeholder={t("common.title")}
className="w-full text-14"
className="nodedc-modal-input w-full !px-4 !py-3 text-13"
tabIndex={getIndex("name")}
autoFocus
/>
@@ -189,7 +190,7 @@ export const ProjectViewForm = observer(function ProjectViewForm(props: Props) {
id="description"
name="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)}
value={value}
onChange={onChange}
@@ -198,7 +199,7 @@ export const ProjectViewForm = observer(function ProjectViewForm(props: Props) {
)}
/>
</div>
<div className="flex gap-2">
<div className="nodedc-work-item-properties-row">
<AccessController control={control} />
<Controller
control={control}
@@ -214,13 +215,26 @@ export const ProjectViewForm = observer(function ProjectViewForm(props: Props) {
})
}
value={displayFilters.layout}
buttonContainerClassName={viewPropertyButtonClassName}
/>
{/* display filters dropdown */}
<Controller
control={control}
name="display_properties"
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
layoutDisplayFiltersOptions={
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 className="flex items-center justify-end gap-2 border-t-[0.5px] border-subtle px-5 py-4">
<Button variant="secondary" size="lg" onClick={handleClose} tabIndex={getIndex("cancel")}>
<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")}
className="nodedc-modal-secondary-button min-w-[8.25rem]"
>
{t("common.cancel")}
</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
? isSubmitting
? t("common.updating")
@@ -88,7 +88,12 @@ export const CreateUpdateProjectViewModal = observer(function CreateUpdateProjec
});
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
data={data}
handleClose={handleClose}
@@ -20,13 +20,16 @@ export const GlobalDefaultViewListItem = observer(function GlobalDefaultViewList
const { t } = useTranslation();
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}`}>
<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 items-center gap-4">
<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>
@@ -13,7 +13,7 @@ import { Button } from "@plane/propel/button";
import type { IIssueDisplayFilterOptions, IIssueDisplayProperties, IWorkspaceView, IIssueFilters } from "@plane/types";
import { EViewAccess, EIssueLayoutTypes, EIssuesStoreType } from "@plane/types";
import { Input, TextArea } from "@plane/ui";
import { getComputedDisplayFilters, getComputedDisplayProperties } from "@plane/utils";
import { cn, getComputedDisplayFilters, getComputedDisplayProperties } from "@plane/utils";
// components
import { DisplayFiltersSelection, FiltersDropdown } from "@/components/issues/issue-layouts/filters";
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"),
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>) => {
await handleFormSubmit(formData);
@@ -75,8 +76,8 @@ export const WorkspaceViewForm = observer(function WorkspaceViewForm(props: Prop
};
return (
<form onSubmit={handleSubmit(handleCreateUpdateView)}>
<div className="space-y-5 p-5">
<form onSubmit={handleSubmit(handleCreateUpdateView)} className="flex flex-col">
<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>
<div className="space-y-3">
<div className="space-y-1">
@@ -100,7 +101,7 @@ export const WorkspaceViewForm = observer(function WorkspaceViewForm(props: Prop
ref={ref}
hasError={Boolean(errors.name)}
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}
placeholder={t("common.description")}
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)}
/>
)}
/>
</div>
<div className="flex gap-2">
<div className="nodedc-work-item-properties-row">
<AccessController control={control} />
{/* display filters dropdown */}
<Controller
@@ -134,7 +135,19 @@ export const WorkspaceViewForm = observer(function WorkspaceViewForm(props: Prop
control={control}
name="display_properties"
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
layoutDisplayFiltersOptions={ISSUE_DISPLAY_FILTERS_BY_PAGE.my_issues.layoutOptions.spreadsheet}
displayFilters={displayFilters ?? {}}
@@ -185,11 +198,16 @@ export const WorkspaceViewForm = observer(function WorkspaceViewForm(props: Prop
</div>
</div>
</div>
<div className="flex items-center justify-end gap-2 border-t-[0.5px] border-subtle px-5 py-4">
<Button variant="secondary" onClick={handleClose}>
<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} className="nodedc-modal-secondary-button min-w-[8.25rem]">
{t("common.cancel")}
</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
? isSubmitting
? t("common.updating")
@@ -104,7 +104,13 @@ export const CreateUpdateWorkspaceViewModal = observer(function CreateUpdateWork
if (!workspaceSlug) return null;
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
handleFormSubmit={handleFormSubmit}
handleClose={handleClose}
@@ -60,21 +60,25 @@ export const GlobalViewListItem = observer(function GlobalViewListItem(props: Pr
<>
<CreateUpdateWorkspaceViewModal data={view} isOpen={updateViewModal} onClose={() => setUpdateViewModal(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}`}>
<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 items-center gap-4">
<div className="flex flex-col">
<p className="truncate text-13 leading-4 font-medium">{truncateText(view.name, 75)}</p>
{view?.description && <p className="text-11 text-secondary">{view.description}</p>}
<p className="truncate text-13 leading-4 font-medium text-primary">{truncateText(view.name, 75)}</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 className="ml-2 flex flex-shrink-0">
<div className="flex items-center gap-4">
<ActionDropdown
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}
/>
</div>