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" ? (
|
||||
<div className="flex h-full overflow-hidden">
|
||||
<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
|
||||
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) => (
|
||||
<Tabs.Trigger
|
||||
key={tab.key}
|
||||
value={tab.key}
|
||||
disabled={tab.isDisabled}
|
||||
size="md"
|
||||
className="h-6 px-3"
|
||||
className="h-7 rounded-full px-3.5"
|
||||
onClick={() => {
|
||||
if (!tab.isDisabled) {
|
||||
handleTabChange(tab.key);
|
||||
|
|
@ -107,7 +107,7 @@ function AnalyticsPage({ params }: Route.ComponentProps) {
|
|||
<Tabs.Content
|
||||
key={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 />
|
||||
</Tabs.Content>
|
||||
|
|
|
|||
|
|
@ -17,7 +17,9 @@ function WorkspaceDraftPage({ params }: Route.ComponentProps) {
|
|||
<>
|
||||
<PageHead title={pageTitle} />
|
||||
<div className="relative h-full w-full overflow-hidden overflow-y-auto">
|
||||
<WorkspaceDraftIssuesRoot workspaceSlug={workspaceSlug} />
|
||||
<div className="nodedc-workspace-page-shell h-full">
|
||||
<WorkspaceDraftIssuesRoot workspaceSlug={workspaceSlug} />
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -13,7 +13,9 @@ export default function WorkspaceStickiesPage() {
|
|||
<>
|
||||
<PageHead title="Your stickies" />
|
||||
<div className="relative h-full w-full overflow-hidden overflow-y-auto">
|
||||
<StickiesInfinite />
|
||||
<div className="nodedc-workspace-page-shell h-full">
|
||||
<StickiesInfinite />
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -15,13 +15,13 @@ import {
|
|||
DEFAULT_GLOBAL_VIEWS_LIST,
|
||||
} from "@plane/constants";
|
||||
import { useTranslation } from "@plane/i18n";
|
||||
import { Button } from "@plane/propel/button";
|
||||
import { ViewsIcon } from "@plane/propel/icons";
|
||||
import type { IIssueDisplayFilterOptions, IIssueDisplayProperties, ICustomSearchSelectOption } from "@plane/types";
|
||||
import { EIssuesStoreType, EIssueLayoutTypes } from "@plane/types";
|
||||
import { Breadcrumbs, Header, BreadcrumbNavigationSearchDropdown } from "@plane/ui";
|
||||
// components
|
||||
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 { DisplayFiltersSelection, FiltersDropdown } from "@/components/issues/issue-layouts/filters";
|
||||
import { WorkItemFiltersToggle } from "@/components/work-item-filters/filters-toggle";
|
||||
|
|
@ -170,14 +170,12 @@ export const GlobalIssuesHeader = observer(function GlobalIssuesHeader() {
|
|||
/>
|
||||
</FiltersDropdown>
|
||||
)}
|
||||
<Button
|
||||
variant="primary"
|
||||
size="lg"
|
||||
<AppHeaderPrimaryActionButton
|
||||
data-ph-element={GLOBAL_VIEW_TRACKER_ELEMENTS.RIGHT_HEADER_ADD_BUTTON}
|
||||
onClick={() => setCreateViewModal(true)}
|
||||
>
|
||||
{t("workspace_views.add_view")}
|
||||
</Button>
|
||||
</AppHeaderPrimaryActionButton>
|
||||
<div className="hidden md:block">
|
||||
{viewDetails && <WorkspaceViewQuickActions workspaceSlug={workspaceSlug?.toString()} view={viewDetails} />}
|
||||
{isDefaultView && defaultViewDetails && (
|
||||
|
|
|
|||
|
|
@ -30,23 +30,25 @@ function WorkspaceViewsPage() {
|
|||
<>
|
||||
<PageHead title={pageTitle} />
|
||||
<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">
|
||||
<SearchIcon className="text-secondary" width={14} height={14} strokeWidth={2} />
|
||||
<Input
|
||||
className="w-full bg-transparent !p-0 text-11 leading-5 text-secondary placeholder:text-placeholder focus:outline-none"
|
||||
value={query}
|
||||
onChange={(e) => setQuery(e.target.value)}
|
||||
placeholder="Search"
|
||||
mode="true-transparent"
|
||||
/>
|
||||
</div>
|
||||
<div className="vertical-scrollbar flex scrollbar-lg h-full w-full flex-col">
|
||||
{DEFAULT_GLOBAL_VIEWS_LIST.filter((v) => t(v.i18n_label).toLowerCase().includes(query.toLowerCase())).map(
|
||||
(option) => (
|
||||
<GlobalDefaultViewListItem key={option.key} view={option} />
|
||||
)
|
||||
)}
|
||||
<GlobalViewsList searchQuery={query} />
|
||||
<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} />
|
||||
<Input
|
||||
className="w-full bg-transparent !p-0 text-13 leading-5 text-primary placeholder:text-placeholder focus:outline-none"
|
||||
value={query}
|
||||
onChange={(e) => setQuery(e.target.value)}
|
||||
placeholder="Search workspace views"
|
||||
mode="true-transparent"
|
||||
/>
|
||||
</div>
|
||||
<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(
|
||||
(option) => (
|
||||
<GlobalDefaultViewListItem key={option.key} view={option} />
|
||||
)
|
||||
)}
|
||||
<GlobalViewsList searchQuery={query} />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -530,6 +530,7 @@
|
|||
outline: none !important;
|
||||
box-shadow: none !important;
|
||||
border-radius: 999px !important;
|
||||
height: 2.5rem !important;
|
||||
min-height: 2.5rem;
|
||||
padding-inline: 1.35rem;
|
||||
background: rgba(18, 18, 22, 0.94) !important;
|
||||
|
|
@ -558,6 +559,7 @@
|
|||
outline: none !important;
|
||||
box-shadow: none !important;
|
||||
border-radius: 999px !important;
|
||||
height: 2.5rem !important;
|
||||
min-height: 2.5rem;
|
||||
padding-inline: 1.55rem;
|
||||
background: rgb(var(--nodedc-accent-rgb)) !important;
|
||||
|
|
@ -573,6 +575,39 @@
|
|||
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 {
|
||||
min-height: 2.75rem;
|
||||
border: 0 !important;
|
||||
|
|
@ -924,6 +959,36 @@
|
|||
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 {
|
||||
@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;
|
||||
}
|
||||
|
||||
.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 {
|
||||
display: flex;
|
||||
width: 100%;
|
||||
|
|
|
|||
Loading…
Reference in New Issue