UI - МЕЖПРОЕКТНАЯ КОММУНИКАЦИЯ: верхняя панель, rich filters и runtime-fix DEBUG=release
This commit is contained in:
parent
b1912c64e9
commit
ae124e50e5
|
|
@ -0,0 +1,7 @@
|
|||
{
|
||||
"nodedc": {
|
||||
"accent_rgb": [195, 255, 102],
|
||||
"passive_card_rgb": [42, 43, 46],
|
||||
"active_card_rgb": [195, 255, 102]
|
||||
}
|
||||
}
|
||||
|
|
@ -23,11 +23,25 @@ from plane.utils.url import is_valid_url
|
|||
|
||||
BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
||||
|
||||
|
||||
def env_flag(value, default=False):
|
||||
if value is None:
|
||||
return default
|
||||
|
||||
normalized = str(value).strip().lower()
|
||||
if normalized in {"1", "true", "yes", "on", "debug"}:
|
||||
return True
|
||||
if normalized in {"0", "false", "no", "off", "release", "prod", "production"}:
|
||||
return False
|
||||
|
||||
return default
|
||||
|
||||
|
||||
# Secret Key
|
||||
SECRET_KEY = os.environ.get("SECRET_KEY", get_random_secret_key())
|
||||
|
||||
# SECURITY WARNING: don't run with debug turned on in production!
|
||||
DEBUG = int(os.environ.get("DEBUG", "0"))
|
||||
DEBUG = env_flag(os.environ.get("DEBUG", "0"))
|
||||
|
||||
# Self-hosted mode
|
||||
IS_SELF_MANAGED = True
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ import os
|
|||
from .common import * # noqa
|
||||
|
||||
# SECURITY WARNING: don't run with debug turned on in production!
|
||||
DEBUG = int(os.environ.get("DEBUG", 0)) == 1
|
||||
DEBUG = env_flag(os.environ.get("DEBUG", 0), default=False)
|
||||
|
||||
# Honor the 'X-Forwarded-Proto' header for request.is_secure()
|
||||
SECURE_PROXY_SSL_HEADER = ("HTTP_X_FORWARDED_PROTO", "https")
|
||||
|
|
|
|||
|
|
@ -108,7 +108,7 @@ export const IssuesHeader = observer(function IssuesHeader() {
|
|||
)}
|
||||
</Header.LeftItem>
|
||||
<Header.RightItem>
|
||||
<div className="hidden gap-2 md:flex">
|
||||
<div className="hidden items-center gap-2 md:flex">
|
||||
<HeaderFilters
|
||||
projectId={projectId}
|
||||
currentProjectDetails={currentProjectDetails}
|
||||
|
|
@ -120,6 +120,7 @@ export const IssuesHeader = observer(function IssuesHeader() {
|
|||
<Button
|
||||
variant="primary"
|
||||
size="lg"
|
||||
className="nodedc-toolbar-primary"
|
||||
onClick={() => {
|
||||
toggleCreateIssueModal(true, EIssuesStoreType.PROJECT);
|
||||
}}
|
||||
|
|
|
|||
|
|
@ -94,47 +94,54 @@ export const HeaderFilters = observer(function HeaderFilters(props: Props) {
|
|||
projectDetails={currentProjectDetails ?? undefined}
|
||||
isEpic={storeType === EIssuesStoreType.EPIC}
|
||||
/>
|
||||
<div className="hidden @4xl:flex">
|
||||
<LayoutSelection
|
||||
layouts={LAYOUTS}
|
||||
onChange={(layout) => handleLayoutChange(layout)}
|
||||
selectedLayout={activeLayout}
|
||||
/>
|
||||
<div className="nodedc-top-toolbar-cluster flex items-center gap-2">
|
||||
<div className="hidden @4xl:flex">
|
||||
<LayoutSelection
|
||||
layouts={LAYOUTS}
|
||||
onChange={(layout) => handleLayoutChange(layout)}
|
||||
selectedLayout={activeLayout}
|
||||
/>
|
||||
</div>
|
||||
<div className="flex @4xl:hidden">
|
||||
<MobileLayoutSelection
|
||||
layouts={LAYOUTS}
|
||||
onChange={(layout) => handleLayoutChange(layout)}
|
||||
activeLayout={activeLayout}
|
||||
/>
|
||||
</div>
|
||||
<WorkItemFiltersToggle entityType={storeType} entityId={projectId} />
|
||||
<FiltersDropdown
|
||||
miniIcon={<SlidersHorizontal className="size-3.5" />}
|
||||
title={t("common.display")}
|
||||
placement="bottom-end"
|
||||
>
|
||||
<DisplayFiltersSelection
|
||||
layoutDisplayFiltersOptions={layoutDisplayFiltersOptions}
|
||||
displayFilters={issueFilters?.displayFilters ?? {}}
|
||||
handleDisplayFiltersUpdate={handleDisplayFilters}
|
||||
displayProperties={issueFilters?.displayProperties ?? {}}
|
||||
handleDisplayPropertiesUpdate={handleDisplayProperties}
|
||||
cycleViewDisabled={!currentProjectDetails?.cycle_view}
|
||||
moduleViewDisabled={!currentProjectDetails?.module_view}
|
||||
isEpic={storeType === EIssuesStoreType.EPIC}
|
||||
/>
|
||||
</FiltersDropdown>
|
||||
{canUserCreateIssue ? (
|
||||
<Button
|
||||
className="nodedc-toolbar-pill hidden md:inline-flex"
|
||||
onClick={() => setAnalyticsModal(true)}
|
||||
variant="secondary"
|
||||
size="lg"
|
||||
>
|
||||
<div className="hidden @4xl:flex">{t("common.analytics")}</div>
|
||||
<div className="flex @4xl:hidden">
|
||||
<ChartNoAxesColumn className="size-3.5" />
|
||||
</div>
|
||||
</Button>
|
||||
) : (
|
||||
<></>
|
||||
)}
|
||||
</div>
|
||||
<div className="flex @4xl:hidden">
|
||||
<MobileLayoutSelection
|
||||
layouts={LAYOUTS}
|
||||
onChange={(layout) => handleLayoutChange(layout)}
|
||||
activeLayout={activeLayout}
|
||||
/>
|
||||
</div>
|
||||
<WorkItemFiltersToggle entityType={storeType} entityId={projectId} />
|
||||
<FiltersDropdown
|
||||
miniIcon={<SlidersHorizontal className="size-3.5" />}
|
||||
title={t("common.display")}
|
||||
placement="bottom-end"
|
||||
>
|
||||
<DisplayFiltersSelection
|
||||
layoutDisplayFiltersOptions={layoutDisplayFiltersOptions}
|
||||
displayFilters={issueFilters?.displayFilters ?? {}}
|
||||
handleDisplayFiltersUpdate={handleDisplayFilters}
|
||||
displayProperties={issueFilters?.displayProperties ?? {}}
|
||||
handleDisplayPropertiesUpdate={handleDisplayProperties}
|
||||
cycleViewDisabled={!currentProjectDetails?.cycle_view}
|
||||
moduleViewDisabled={!currentProjectDetails?.module_view}
|
||||
isEpic={storeType === EIssuesStoreType.EPIC}
|
||||
/>
|
||||
</FiltersDropdown>
|
||||
{canUserCreateIssue ? (
|
||||
<Button className="hidden px-2 md:block" onClick={() => setAnalyticsModal(true)} variant="secondary" size="lg">
|
||||
<div className="hidden @4xl:flex">{t("common.analytics")}</div>
|
||||
<div className="flex @4xl:hidden">
|
||||
<ChartNoAxesColumn className="size-3.5" />
|
||||
</div>
|
||||
</Button>
|
||||
) : (
|
||||
<></>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
});
|
||||
|
|
|
|||
|
|
@ -71,10 +71,10 @@ export const FilterDisplayProperties = observer(function FilterDisplayProperties
|
|||
<button
|
||||
key={displayProperty.key}
|
||||
type="button"
|
||||
className={`rounded-sm border px-2 py-0.5 text-11 transition-all ${
|
||||
className={`rounded-full border-0 px-3 py-1.5 text-12 transition-all ${
|
||||
displayProperties?.[displayProperty.key]
|
||||
? "border-accent-strong bg-accent-primary text-on-color"
|
||||
: "border-subtle hover:bg-layer-1"
|
||||
? "bg-[rgb(var(--nodedc-accent-rgb))] text-[#0b1117]"
|
||||
: "bg-white/5 text-secondary hover:bg-white/8"
|
||||
}`}
|
||||
onClick={() =>
|
||||
handleUpdate({
|
||||
|
|
|
|||
|
|
@ -61,11 +61,12 @@ export function FiltersDropdown(props: Props) {
|
|||
variant="secondary"
|
||||
prependIcon={icon}
|
||||
tabIndex={tabIndex}
|
||||
className="relative"
|
||||
className="nodedc-toolbar-pill relative"
|
||||
data-active={open}
|
||||
size="lg"
|
||||
>
|
||||
<>
|
||||
<div className={`${open ? "text-primary" : "text-secondary"}`}>
|
||||
<div className={`${open ? "text-[rgb(var(--nodedc-accent-rgb))]" : "text-secondary"}`}>
|
||||
<span>{title}</span>
|
||||
</div>
|
||||
{isFiltersApplied && (
|
||||
|
|
@ -80,6 +81,7 @@ export function FiltersDropdown(props: Props) {
|
|||
ref={setReferenceElement}
|
||||
variant="secondary"
|
||||
tabIndex={tabIndex}
|
||||
className="nodedc-toolbar-pill"
|
||||
size="lg"
|
||||
>
|
||||
{miniIcon || title}
|
||||
|
|
@ -100,7 +102,7 @@ export function FiltersDropdown(props: Props) {
|
|||
{/** translate-y-0 is a hack to create new stacking context. Required for safari */}
|
||||
<Popover.Panel className="fixed z-10 translate-y-0">
|
||||
<div
|
||||
className="my-1 overflow-hidden rounded-sm border border-subtle bg-surface-1 shadow-raised-100"
|
||||
className="nodedc-dropdown-surface my-1 overflow-hidden"
|
||||
ref={setPopperElement}
|
||||
style={styles.popper}
|
||||
{...attributes.popper}
|
||||
|
|
|
|||
|
|
@ -15,11 +15,11 @@ type Props = {
|
|||
|
||||
export function FilterHeader({ title, isPreviewEnabled, handleIsPreviewEnabled }: Props) {
|
||||
return (
|
||||
<div className="sticky top-0 flex items-center justify-between gap-2 bg-surface-1">
|
||||
<div className="sticky top-0 flex items-center justify-between gap-2 bg-transparent pb-1">
|
||||
<div className="flex-grow truncate text-caption-sm-medium text-placeholder">{title}</div>
|
||||
<button
|
||||
type="button"
|
||||
className="grid h-5 w-5 flex-shrink-0 place-items-center rounded-sm hover:bg-layer-transparent-hover"
|
||||
className="nodedc-toolbar-icon-button grid h-7 w-7 flex-shrink-0 place-items-center"
|
||||
onClick={handleIsPreviewEnabled}
|
||||
>
|
||||
{isPreviewEnabled ? <ChevronUpIcon height={14} width={14} /> : <ChevronDownIcon height={14} width={14} />}
|
||||
|
|
|
|||
|
|
@ -16,18 +16,20 @@ type Props = {
|
|||
};
|
||||
|
||||
export function FilterOption(props: Props) {
|
||||
const { icon, isChecked, multiple = true, onClick, title, activePulse = false } = props;
|
||||
const { icon, isChecked, onClick, title, activePulse = false } = props;
|
||||
|
||||
return (
|
||||
<button
|
||||
type="button"
|
||||
className="flex w-full items-center gap-2 rounded-sm p-1.5 hover:bg-layer-transparent-hover"
|
||||
className="nodedc-dropdown-option"
|
||||
onClick={onClick}
|
||||
>
|
||||
<div
|
||||
className={`grid h-3 w-3 flex-shrink-0 place-items-center border ${
|
||||
isChecked ? "border-accent-strong bg-accent-primary text-on-color" : "border-strong"
|
||||
} ${multiple ? "rounded-xs" : "rounded-full"}`}
|
||||
className={`grid h-4 w-4 flex-shrink-0 place-items-center border-0 ${
|
||||
isChecked
|
||||
? "bg-[rgb(var(--nodedc-accent-rgb))] text-[#0b1117]"
|
||||
: "bg-white/6 text-transparent"
|
||||
} rounded-full`}
|
||||
>
|
||||
{isChecked && <CheckIcon width={10} height={10} strokeWidth={3} />}
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -32,25 +32,25 @@ export function LayoutSelection(props: Props) {
|
|||
};
|
||||
|
||||
return (
|
||||
<div className="flex items-center gap-1 rounded-md bg-layer-3 p-1">
|
||||
<div className="nodedc-toolbar-group flex items-center gap-1">
|
||||
{ISSUE_LAYOUTS.filter((l) => layouts.includes(l.key)).map((layout) => (
|
||||
<Tooltip key={layout.key} tooltipContent={t(layout.i18n_title)} isMobile={isMobile}>
|
||||
<button
|
||||
type="button"
|
||||
className={cn(
|
||||
"group grid h-5.5 w-7 place-items-center overflow-hidden rounded-sm transition-all hover:bg-layer-transparent-hover",
|
||||
{
|
||||
"bg-layer-transparent-active hover:bg-layer-transparent-active": selectedLayout === layout.key,
|
||||
}
|
||||
"nodedc-toolbar-icon-button group grid h-8 w-8 place-items-center overflow-hidden"
|
||||
)}
|
||||
data-active={selectedLayout === layout.key}
|
||||
onClick={() => handleOnChange(layout.key)}
|
||||
>
|
||||
<IssueLayoutIcon
|
||||
layout={layout.key}
|
||||
size={14}
|
||||
strokeWidth={2}
|
||||
className={`size-3.5 ${selectedLayout == layout.key ? "text-primary" : "text-secondary"}`}
|
||||
/>
|
||||
<span className="nodedc-toolbar-icon-active-dot">
|
||||
<IssueLayoutIcon
|
||||
layout={layout.key}
|
||||
size={14}
|
||||
strokeWidth={2}
|
||||
className={`size-3.5 ${selectedLayout == layout.key ? "text-[#0b1117]" : "text-secondary"}`}
|
||||
/>
|
||||
</span>
|
||||
</button>
|
||||
</Tooltip>
|
||||
))}
|
||||
|
|
|
|||
|
|
@ -29,10 +29,12 @@ export function MobileLayoutSelection({
|
|||
className="flex flex-grow justify-center text-13 text-secondary"
|
||||
placement="bottom-start"
|
||||
customButton={
|
||||
<Button variant="secondary" className="relative px-2">
|
||||
{activeLayout && (
|
||||
<IssueLayoutIcon layout={activeLayout} size={14} strokeWidth={2} className={`h-3.5 w-3.5`} />
|
||||
)}
|
||||
<Button variant="secondary" className="nodedc-toolbar-pill relative gap-2 px-3">
|
||||
<span className="nodedc-toolbar-icon-active-dot bg-[rgb(var(--nodedc-accent-rgb))] text-[#0b1117]">
|
||||
{activeLayout && (
|
||||
<IssueLayoutIcon layout={activeLayout} size={14} strokeWidth={2} className={`h-3.5 w-3.5`} />
|
||||
)}
|
||||
</span>
|
||||
<ChevronDownIcon className="my-auto size-3 text-secondary" strokeWidth={2} />
|
||||
</Button>
|
||||
}
|
||||
|
|
|
|||
|
|
@ -312,12 +312,14 @@ export const KanbanIssueBlock = observer(function KanbanIssueBlock(props: IssueB
|
|||
: "rounded-lg border border-subtle bg-layer-2 p-3 shadow-raised-100 outline-[0.5px] outline-transparent hover:border-strong hover:shadow-raised-200",
|
||||
{ "hover:cursor-pointer": isDragAllowed },
|
||||
{
|
||||
"bg-[#C3FF66] text-[#111111]": cardVariant === "internal-contour" && isPeeked,
|
||||
"bg-[#2A2B2E] text-white": cardVariant === "internal-contour" && !isPeeked,
|
||||
"bg-[rgb(var(--nodedc-card-active-rgb))] text-[#111111]":
|
||||
cardVariant === "internal-contour" && isPeeked,
|
||||
"bg-[rgb(var(--nodedc-card-passive-rgb))] text-white":
|
||||
cardVariant === "internal-contour" && !isPeeked,
|
||||
"border border-accent-strong hover:border-accent-strong": cardVariant !== "internal-contour" && isPeeked,
|
||||
},
|
||||
{ "z-[100] bg-layer-1": isCurrentBlockDragging && cardVariant !== "internal-contour" },
|
||||
{ "z-[100] bg-[#C3FF66]": isCurrentBlockDragging && cardVariant === "internal-contour" }
|
||||
{ "z-[100] bg-[rgb(var(--nodedc-card-active-rgb))]": isCurrentBlockDragging && cardVariant === "internal-contour" }
|
||||
)}
|
||||
onClick={() => handleIssuePeekOverview(issue)}
|
||||
disabled={!!issue?.tempId}
|
||||
|
|
|
|||
|
|
@ -76,8 +76,9 @@ export const InternalContourKanbanCard = observer(function InternalContourKanban
|
|||
const projectStateIds = issue.project_id ? getProjectStateIds(issue.project_id) : [];
|
||||
const foregroundClasses = isActive ? "text-[#111111]" : "text-white";
|
||||
const subtleTextClasses = isActive ? "text-[#2F4721]" : "text-[#B3B3B8]";
|
||||
const pillBackgroundClasses = isActive ? "bg-black/10 text-[#111111]" : "bg-[#1B1B1F] text-white";
|
||||
const iconBubbleClasses = isActive ? "bg-black text-[#C3FF66]" : "bg-[#111214] text-white";
|
||||
const pillBackgroundClasses =
|
||||
isActive ? "bg-black/10 text-[#111111]" : "bg-[rgb(var(--nodedc-card-passive-rgb))] text-white";
|
||||
const iconBubbleClasses = isActive ? "bg-black text-[rgb(var(--nodedc-card-active-rgb))]" : "bg-[#111214] text-white";
|
||||
const statusIconColor = selectedState?.color ?? (isActive ? "#111111" : "var(--text-color-primary)");
|
||||
|
||||
const handleEventPropagation = (e: React.MouseEvent) => {
|
||||
|
|
@ -95,7 +96,9 @@ export const InternalContourKanbanCard = observer(function InternalContourKanban
|
|||
ref={menuActionRef}
|
||||
className={cn(
|
||||
"flex h-8 w-8 cursor-pointer items-center justify-center rounded-full p-1 transition-colors",
|
||||
isActive ? "bg-black text-[#C3FF66] hover:bg-black/90" : "bg-[#111214] text-white hover:bg-[#0A0B0C]",
|
||||
isActive
|
||||
? "bg-black text-[rgb(var(--nodedc-card-active-rgb))] hover:bg-black/90"
|
||||
: "bg-[#111214] text-white hover:bg-[#0A0B0C]",
|
||||
isMenuActive && (isActive ? "bg-black/90" : "bg-[#0A0B0C]")
|
||||
)}
|
||||
onClick={() => setIsMenuActive(!isMenuActive)}
|
||||
|
|
|
|||
|
|
@ -68,12 +68,14 @@ export const AddFilterButton = observer(function AddFilterButton<P extends TFilt
|
|||
{...props}
|
||||
buttonConfig={{
|
||||
...buttonConfig,
|
||||
className: cn(getButtonStyling(variant, size), "py-[5px]", className),
|
||||
className: cn(getButtonStyling(variant, size), "nodedc-toolbar-filter-toggle", className),
|
||||
}}
|
||||
handleFilterSelect={handleFilterSelect}
|
||||
customButton={
|
||||
<div className="flex items-center gap-1">
|
||||
{iconConfig.shouldShowIcon && <FilterIcon className="size-4 text-secondary" />}
|
||||
<div className={cn("flex items-center", label ? "gap-1" : "justify-center")}>
|
||||
{iconConfig.shouldShowIcon && (
|
||||
<FilterIcon className={cn("size-4 text-secondary", !label && "translate-y-[3px]")} />
|
||||
)}
|
||||
{label}
|
||||
</div>
|
||||
}
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@ import type { IFilterInstance } from "@plane/shared-state";
|
|||
import type { TExternalFilter, TFilterProperty, TSupportedOperators } from "@plane/types";
|
||||
import { CustomSearchSelect } from "@plane/ui";
|
||||
import { getOperatorForPayload } from "@plane/utils";
|
||||
import { localizeRichFilterLabel } from "../i18n";
|
||||
|
||||
export type TAddFilterDropdownProps<P extends TFilterProperty, E extends TExternalFilter> = {
|
||||
customButton: React.ReactNode;
|
||||
|
|
@ -40,12 +41,12 @@ export const AddFilterDropdown = observer(function AddFilterDropdown<
|
|||
{config.icon && (
|
||||
<config.icon className="size-4 text-tertiary transition-transform duration-200 ease-in-out" />
|
||||
)}
|
||||
<span>{config.label}</span>
|
||||
<span>{localizeRichFilterLabel(config.label)}</span>
|
||||
</div>
|
||||
{config.rightContent}
|
||||
</div>
|
||||
),
|
||||
query: config.label.toLowerCase(),
|
||||
query: localizeRichFilterLabel(config.label).toLowerCase(),
|
||||
}));
|
||||
|
||||
// If all filters are applied, show disabled options
|
||||
|
|
@ -54,8 +55,8 @@ export const AddFilterDropdown = observer(function AddFilterDropdown<
|
|||
? [
|
||||
{
|
||||
value: "all_filters_applied",
|
||||
content: <div className="text-placeholder italic">All filters applied</div>,
|
||||
query: "all filters applied",
|
||||
content: <div className="text-placeholder italic">Все фильтры уже применены</div>,
|
||||
query: "все фильтры уже применены",
|
||||
disabled: true,
|
||||
},
|
||||
]
|
||||
|
|
|
|||
|
|
@ -29,7 +29,7 @@ export const FilterItemCloseButton = observer(function FilterItemCloseButton<
|
|||
return (
|
||||
<button
|
||||
onClick={handleRemoveFilter}
|
||||
className="bg-layer-transparent px-1.5 text-placeholder hover:bg-layer-transparent-hover hover:text-tertiary focus:outline-none"
|
||||
className="rounded-r-full bg-transparent px-2 text-placeholder hover:bg-white/6 hover:text-tertiary focus:outline-none"
|
||||
type="button"
|
||||
aria-label="Remove filter"
|
||||
>
|
||||
|
|
|
|||
|
|
@ -58,9 +58,9 @@ export function FilterItemContainer(props: FilterItemContainerProps) {
|
|||
<Tooltip tooltipContent={tooltipContent} position="bottom" disabled={!tooltipContent}>
|
||||
<div
|
||||
ref={itemRef}
|
||||
className={cn("flex h-7 items-stretch overflow-hidden rounded-sm border transition-all duration-200", {
|
||||
"border-subtle bg-surface-1": variant === "default",
|
||||
"border-danger-strong bg-surface-2": variant === "error",
|
||||
className={cn("flex min-h-9 items-stretch overflow-hidden rounded-full transition-all duration-200", {
|
||||
"nodedc-filter-chip": variant === "default",
|
||||
"bg-[#3b1212]/80 text-[#ffd6d6] rounded-full": variant === "error",
|
||||
})}
|
||||
>
|
||||
{children}
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@ import type { IFilterInstance } from "@plane/shared-state";
|
|||
import type { TExternalFilter, TFilterProperty, TSupportedOperators } from "@plane/types";
|
||||
// local imports
|
||||
import { AddFilterDropdown } from "../add-filters/dropdown";
|
||||
import { localizeRichFilterLabel } from "../i18n";
|
||||
import { COMMON_FILTER_ITEM_BORDER_CLASSNAME } from "../shared";
|
||||
|
||||
interface IFilterItemPropertyProps<P extends TFilterProperty, E extends TExternalFilter> {
|
||||
|
|
@ -52,12 +53,13 @@ type TPropertyButtonProps<P extends TFilterProperty, E extends TExternalFilter>
|
|||
|
||||
function PropertyButton<P extends TFilterProperty, E extends TExternalFilter>(props: TPropertyButtonProps<P, E>) {
|
||||
const { icon: Icon, label, tooltipContent, className } = props;
|
||||
const localizedLabel = localizeRichFilterLabel(label);
|
||||
|
||||
return (
|
||||
<Tooltip tooltipContent={tooltipContent} position="bottom-start" disabled={!tooltipContent}>
|
||||
<div
|
||||
className={cn(
|
||||
"flex h-full min-w-0 items-center gap-1 px-2 py-[5px] text-11 text-tertiary",
|
||||
"flex h-full min-w-0 items-center gap-1.5 px-3 py-[7px] text-13 text-tertiary",
|
||||
COMMON_FILTER_ITEM_BORDER_CLASSNAME,
|
||||
className
|
||||
)}
|
||||
|
|
@ -67,7 +69,7 @@ function PropertyButton<P extends TFilterProperty, E extends TExternalFilter>(pr
|
|||
<Icon className="size-3.5" />
|
||||
</div>
|
||||
)}
|
||||
<span className="truncate">{label}</span>
|
||||
<span className="truncate">{localizedLabel}</span>
|
||||
</div>
|
||||
</Tooltip>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -20,6 +20,7 @@ import { CustomSearchSelect } from "@plane/ui";
|
|||
import { cn, getOperatorForPayload } from "@plane/utils";
|
||||
// local imports
|
||||
import { FilterValueInput } from "../filter-value-input/root";
|
||||
import { localizeRichFilterOperator } from "../i18n";
|
||||
import { COMMON_FILTER_ITEM_BORDER_CLASSNAME } from "../shared";
|
||||
import { FilterItemCloseButton } from "./close-button";
|
||||
import { FilterItemContainer } from "./container";
|
||||
|
|
@ -44,8 +45,8 @@ export const FilterItem = observer(function FilterItem<P extends TFilterProperty
|
|||
?.getAllDisplayOperatorOptionsByValue(condition.value as TFilterValue)
|
||||
.map((option) => ({
|
||||
value: option.value,
|
||||
content: option.label,
|
||||
query: option.label.toLowerCase(),
|
||||
content: localizeRichFilterOperator(option.label),
|
||||
query: localizeRichFilterOperator(option.label).toLowerCase(),
|
||||
}));
|
||||
const selectedOperatorFieldConfig = filterConfig?.getOperatorConfig(condition.operator);
|
||||
const selectedOperatorOption = filterConfig?.getDisplayOperatorByValue(
|
||||
|
|
@ -102,15 +103,15 @@ export const FilterItem = observer(function FilterItem<P extends TFilterProperty
|
|||
options={operatorOptions}
|
||||
className={COMMON_FILTER_ITEM_BORDER_CLASSNAME}
|
||||
customButtonClassName={cn(
|
||||
"h-full px-2 text-13 font-regular",
|
||||
isOperatorSelectionDisabled && "hover:bg-layer-2-hover"
|
||||
"h-full px-3 text-13 font-regular",
|
||||
isOperatorSelectionDisabled && "hover:bg-transparent"
|
||||
)}
|
||||
optionsClassName="w-48"
|
||||
maxHeight="2xl"
|
||||
disabled={isOperatorSelectionDisabled}
|
||||
customButton={
|
||||
<div className="flex h-full items-center" aria-disabled={isOperatorSelectionDisabled}>
|
||||
{filterConfig.getLabelForOperator(selectedOperatorOption)}
|
||||
{localizeRichFilterOperator(filterConfig.getLabelForOperator(selectedOperatorOption))}
|
||||
</div>
|
||||
}
|
||||
/>
|
||||
|
|
|
|||
|
|
@ -58,7 +58,7 @@ export function SelectedOptionsDisplay<V extends TFilterValue>(props: TSelectedO
|
|||
enterTo="opacity-100"
|
||||
className="ml-1 whitespace-nowrap text-tertiary"
|
||||
>
|
||||
+{remainingCount} more
|
||||
+{remainingCount} еще
|
||||
</Transition>
|
||||
)}
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -51,7 +51,7 @@ export const getFormattedOptions = <V extends TFilterValue>(options: IFilterOpti
|
|||
|
||||
export const getCommonCustomSearchSelectProps = (isDisabled?: boolean) => ({
|
||||
customButtonClassName: cn(
|
||||
"h-full w-full px-2 text-13 font-regular transition-all duration-300 ease-in-out",
|
||||
"h-full w-full px-3 text-13 font-regular transition-all duration-300 ease-in-out",
|
||||
!isDisabled && COMMON_FILTER_ITEM_BORDER_CLASSNAME,
|
||||
isDisabled && "hover:bg-surface-1"
|
||||
),
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ import { observer } from "mobx-react";
|
|||
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";
|
||||
|
|
@ -40,6 +41,7 @@ export const FiltersRow = observer(function FiltersRow<K extends TFilterProperty
|
|||
variant = "header",
|
||||
trackerElements,
|
||||
} = props;
|
||||
const { t } = useTranslation();
|
||||
// states
|
||||
const [isUpdating, setIsUpdating] = useState(false);
|
||||
// derived values
|
||||
|
|
@ -53,7 +55,7 @@ export const FiltersRow = observer(function FiltersRow<K extends TFilterProperty
|
|||
};
|
||||
|
||||
const modalButtonConfig: Partial<TAddFilterButtonProps<K, E>["buttonConfig"]> = {
|
||||
label: !hasAnyConditions ? "Filters" : null,
|
||||
label: !hasAnyConditions ? t("common.filters") : null,
|
||||
};
|
||||
|
||||
const handleUpdate = useCallback(async () => {
|
||||
|
|
@ -92,44 +94,44 @@ export const FiltersRow = observer(function FiltersRow<K extends TFilterProperty
|
|||
<ElementTransition show={filter.canClearFilters}>
|
||||
<Button
|
||||
variant="secondary"
|
||||
className={COMMON_OPERATION_BUTTON_CLASSNAME}
|
||||
className={cn(COMMON_OPERATION_BUTTON_CLASSNAME, "nodedc-filter-clear-button")}
|
||||
onClick={filter.clearFilters}
|
||||
data-ph-element={trackerElements?.clearFilter}
|
||||
>
|
||||
{filter.clearFilterOptions?.label ?? "Clear all"}
|
||||
{t("common.clear")}
|
||||
</Button>
|
||||
</ElementTransition>
|
||||
<ElementTransition show={filter.canSaveView}>
|
||||
<Button
|
||||
variant="secondary"
|
||||
className={COMMON_OPERATION_BUTTON_CLASSNAME}
|
||||
className={cn(COMMON_OPERATION_BUTTON_CLASSNAME, "nodedc-filter-clear-button")}
|
||||
onClick={filter.saveView}
|
||||
data-ph-element={trackerElements?.saveView}
|
||||
>
|
||||
{filter.saveViewOptions?.label ?? "Save view"}
|
||||
{filter.saveViewOptions?.label ?? "Сохранить вид"}
|
||||
</Button>
|
||||
</ElementTransition>
|
||||
<ElementTransition show={filter.canUpdateView}>
|
||||
<Button
|
||||
variant="secondary"
|
||||
className={COMMON_OPERATION_BUTTON_CLASSNAME}
|
||||
className={cn(COMMON_OPERATION_BUTTON_CLASSNAME, "nodedc-filter-clear-button")}
|
||||
onClick={handleUpdate}
|
||||
loading={isUpdating}
|
||||
disabled={isUpdating}
|
||||
data-ph-element={trackerElements?.updateView}
|
||||
>
|
||||
{isUpdating ? "Confirming" : (filter.updateViewOptions?.label ?? "Update view")}
|
||||
{isUpdating ? "Подтверждение..." : (filter.updateViewOptions?.label ?? "Обновить вид")}
|
||||
</Button>
|
||||
</ElementTransition>
|
||||
</>
|
||||
);
|
||||
|
||||
const mainContent = (
|
||||
<div className="flex w-full items-start gap-2 rounded-lg bg-layer-1 px-4 py-2">
|
||||
<div className="nodedc-filter-row-shell flex w-full items-start 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 border-l border-subtle pl-4", {
|
||||
"border-l-transparent pl-0": !hasAvailableOperations,
|
||||
className={cn("flex items-center gap-2", {
|
||||
"hidden": !hasAvailableOperations,
|
||||
})}
|
||||
>
|
||||
{rightContent}
|
||||
|
|
@ -137,12 +139,10 @@ 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 rounded-lg bg-layer-1 p-2">{mainContent}</div>
|
||||
);
|
||||
const ModalVariant = <div className="flex min-h-11 w-full flex-wrap items-center gap-2 p-2">{mainContent}</div>;
|
||||
|
||||
const HeaderVariant = (
|
||||
<Header variant={EHeaderVariant.TERNARY} className="min-h-11 bg-surface-1 !px-3">
|
||||
<Header variant={EHeaderVariant.TERNARY} className="min-h-11 bg-transparent !px-0">
|
||||
{mainContent}
|
||||
</Header>
|
||||
);
|
||||
|
|
@ -160,7 +160,7 @@ export const FiltersRow = observer(function FiltersRow<K extends TFilterProperty
|
|||
return <RowTransition show={filter.isVisible}>{variant === "modal" ? ModalVariant : HeaderVariant}</RowTransition>;
|
||||
});
|
||||
|
||||
const COMMON_OPERATION_BUTTON_CLASSNAME = "py-1";
|
||||
const COMMON_OPERATION_BUTTON_CLASSNAME = "min-h-9 px-4 py-1";
|
||||
|
||||
type TElementTransitionProps = {
|
||||
children: React.ReactNode;
|
||||
|
|
|
|||
|
|
@ -19,7 +19,7 @@ type TFiltersToggleProps<P extends TFilterProperty, E extends TExternalFilter> =
|
|||
};
|
||||
|
||||
const COMMON_CLASSNAME =
|
||||
"grid place-items-center h-7 w-full py-0.5 px-2 rounded-md border border-subtle-1 transition-all duration-200 cursor-pointer";
|
||||
"nodedc-toolbar-filter-toggle grid h-8 w-8 place-items-center px-0 py-0 transition-all duration-200 cursor-pointer";
|
||||
|
||||
export const FiltersToggle = observer(function FiltersToggle<P extends TFilterProperty, E extends TExternalFilter>(
|
||||
props: TFiltersToggleProps<P, E>
|
||||
|
|
@ -40,26 +40,13 @@ export const FiltersToggle = observer(function FiltersToggle<P extends TFilterPr
|
|||
filter.toggleVisibility();
|
||||
};
|
||||
|
||||
// Base classes when filter is active
|
||||
const activeFilterBaseClasses =
|
||||
"text-accent-primary border border-accent-subtle-1 hover:border-accent-subtle-1 active:border-accent-subtle-1 focus:border-accent-subtle-1";
|
||||
|
||||
// State classes that prevent hover/active/focus color changes
|
||||
const noHoverStateClasses = "hover:text-accent-primary active:text-accent-primary focus:text-accent-primary";
|
||||
|
||||
// Background classes based on toggle state (darker when open, lighter when closed)
|
||||
const backgroundClasses = isFilterRowVisible
|
||||
? "bg-accent-subtle-hover hover:bg-accent-subtle-hover active:bg-accent-subtle-hover focus:bg-accent-subtle-hover"
|
||||
: "bg-accent-subtle hover:bg-accent-subtle active:bg-accent-subtle focus:bg-accent-subtle";
|
||||
|
||||
const buttonClassName = cn({
|
||||
[activeFilterBaseClasses]: showFilterRowChangesPill,
|
||||
[backgroundClasses]: showFilterRowChangesPill,
|
||||
[noHoverStateClasses]: showFilterRowChangesPill,
|
||||
"nodedc-toolbar-filter-toggle": true,
|
||||
"text-[rgb(var(--nodedc-accent-rgb))]": showFilterRowChangesPill,
|
||||
});
|
||||
|
||||
const iconClassName = cn({
|
||||
"text-accent-primary [&_path]:fill-current": showFilterRowChangesPill,
|
||||
"text-[rgb(var(--nodedc-accent-rgb))] [&_path]:fill-current": showFilterRowChangesPill,
|
||||
});
|
||||
|
||||
// Show the add filter button when there are no active conditions, the filter row is hidden, and no unsaved changes exist
|
||||
|
|
@ -84,7 +71,8 @@ export const FiltersToggle = observer(function FiltersToggle<P extends TFilterPr
|
|||
icon={showFilterRowChangesPill ? FilterAppliedIcon : FilterIcon}
|
||||
onClick={handleToggleFilter}
|
||||
className={buttonClassName}
|
||||
iconClassName={iconClassName}
|
||||
data-active={showFilterRowChangesPill}
|
||||
iconClassName={cn("translate-y-[3px]", iconClassName)}
|
||||
/>
|
||||
);
|
||||
});
|
||||
|
|
|
|||
|
|
@ -0,0 +1,35 @@
|
|||
const FILTER_LABEL_MAP: Record<string, string> = {
|
||||
Assignees: "Назначенные",
|
||||
Assignee: "Назначенный",
|
||||
Priority: "Приоритет",
|
||||
State: "Статус",
|
||||
"State Group": "Группа статусов",
|
||||
"State Groups": "Группы статусов",
|
||||
Mentions: "Упоминания",
|
||||
Label: "Метка",
|
||||
Labels: "Метки",
|
||||
Project: "Проект",
|
||||
"Created by": "Автор",
|
||||
"Created at": "Дата создания",
|
||||
"Updated at": "Дата обновления",
|
||||
"Start date": "Дата начала",
|
||||
"Target date": "Срок выполнения",
|
||||
"Due date": "Срок выполнения",
|
||||
Parent: "Родительский",
|
||||
};
|
||||
|
||||
const FILTER_OPERATOR_MAP: Record<string, string> = {
|
||||
is: "равно",
|
||||
"is any of": "содержит одно из",
|
||||
between: "между",
|
||||
};
|
||||
|
||||
export const localizeRichFilterLabel = (label: string | undefined) => {
|
||||
if (!label) return "";
|
||||
return FILTER_LABEL_MAP[label] ?? label;
|
||||
};
|
||||
|
||||
export const localizeRichFilterOperator = (label: string | undefined) => {
|
||||
if (!label) return "";
|
||||
return FILTER_OPERATOR_MAP[label] ?? label;
|
||||
};
|
||||
|
|
@ -12,7 +12,7 @@ import type {
|
|||
TSupportedFilterFieldConfigs,
|
||||
} from "@plane/types";
|
||||
|
||||
export const COMMON_FILTER_ITEM_BORDER_CLASSNAME = "border-r border-subtle-1";
|
||||
export const COMMON_FILTER_ITEM_BORDER_CLASSNAME = "nodedc-filter-chip-separator";
|
||||
|
||||
export const EMPTY_FILTER_PLACEHOLDER_TEXT = "--";
|
||||
|
||||
|
|
|
|||
|
|
@ -27,6 +27,9 @@
|
|||
--editor-colors-light-blue-background: #c5eff9;
|
||||
--editor-colors-dark-blue-background: #c9dafb;
|
||||
--editor-colors-purple-background: #e3d8fd;
|
||||
--nodedc-accent-rgb: 51 163 255;
|
||||
--nodedc-card-passive-rgb: 42 43 46;
|
||||
--nodedc-card-active-rgb: 195 255 102;
|
||||
/* end background colors */
|
||||
}
|
||||
/* background colors */
|
||||
|
|
@ -354,6 +357,169 @@
|
|||
@apply bg-white/6;
|
||||
}
|
||||
|
||||
.nodedc-toolbar-group {
|
||||
background: rgba(8, 8, 11, 0.92);
|
||||
border-radius: 999px;
|
||||
padding: 0.25rem;
|
||||
border: 0 !important;
|
||||
box-shadow: none !important;
|
||||
isolation: isolate;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.nodedc-toolbar-icon-button {
|
||||
border: 0 !important;
|
||||
outline: none !important;
|
||||
box-shadow: none !important;
|
||||
border-radius: 999px !important;
|
||||
background: transparent !important;
|
||||
color: rgba(255, 255, 255, 0.72);
|
||||
transition:
|
||||
color 160ms ease,
|
||||
background-color 160ms ease,
|
||||
transform 160ms ease;
|
||||
}
|
||||
|
||||
.nodedc-toolbar-icon-button:hover {
|
||||
background: rgba(255, 255, 255, 0.05) !important;
|
||||
color: rgba(255, 255, 255, 0.94);
|
||||
}
|
||||
|
||||
.nodedc-toolbar-icon-button[data-active="true"] {
|
||||
background: transparent !important;
|
||||
color: rgba(255, 255, 255, 0.98);
|
||||
}
|
||||
|
||||
.nodedc-toolbar-icon-active-dot {
|
||||
display: grid;
|
||||
height: 2rem;
|
||||
width: 2rem;
|
||||
place-items: center;
|
||||
border-radius: 999px;
|
||||
transition:
|
||||
background-color 160ms ease,
|
||||
color 160ms ease;
|
||||
}
|
||||
|
||||
.nodedc-toolbar-icon-button[data-active="true"] .nodedc-toolbar-icon-active-dot {
|
||||
background: rgb(var(--nodedc-accent-rgb));
|
||||
color: #0b1117;
|
||||
}
|
||||
|
||||
.nodedc-toolbar-pill {
|
||||
position: relative;
|
||||
display: inline-flex !important;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
flex-shrink: 0;
|
||||
isolation: isolate;
|
||||
overflow: hidden;
|
||||
border: 0 !important;
|
||||
outline: none !important;
|
||||
box-shadow: none !important;
|
||||
border-radius: 999px !important;
|
||||
min-height: 2.5rem;
|
||||
padding-inline: 1rem;
|
||||
background: rgba(18, 18, 22, 0.94) !important;
|
||||
color: var(--text-color-primary) !important;
|
||||
transition:
|
||||
background-color 160ms ease,
|
||||
color 160ms ease,
|
||||
transform 160ms ease;
|
||||
}
|
||||
|
||||
.nodedc-toolbar-pill:hover {
|
||||
background: rgba(24, 24, 29, 0.96) !important;
|
||||
}
|
||||
|
||||
.nodedc-toolbar-pill[data-active="true"] {
|
||||
color: rgb(var(--nodedc-accent-rgb)) !important;
|
||||
}
|
||||
|
||||
.nodedc-toolbar-primary {
|
||||
border: 0 !important;
|
||||
outline: none !important;
|
||||
box-shadow: none !important;
|
||||
border-radius: 999px !important;
|
||||
min-height: 2.5rem;
|
||||
padding-inline: 1rem;
|
||||
background: rgb(var(--nodedc-accent-rgb)) !important;
|
||||
color: #0b1117 !important;
|
||||
}
|
||||
|
||||
.nodedc-toolbar-primary:hover {
|
||||
background: rgba(var(--nodedc-accent-rgb), 0.92) !important;
|
||||
}
|
||||
|
||||
.nodedc-toolbar-filter-toggle {
|
||||
display: grid;
|
||||
height: 2.5rem !important;
|
||||
width: 2.5rem !important;
|
||||
place-items: center;
|
||||
flex-shrink: 0;
|
||||
border: 0 !important;
|
||||
outline: none !important;
|
||||
box-shadow: none !important;
|
||||
border-radius: 999px !important;
|
||||
background: transparent !important;
|
||||
color: rgba(255, 255, 255, 0.72) !important;
|
||||
transition: color 160ms ease;
|
||||
}
|
||||
|
||||
.nodedc-toolbar-filter-toggle:hover,
|
||||
.nodedc-toolbar-filter-toggle:focus-visible {
|
||||
background: transparent !important;
|
||||
color: rgba(255, 255, 255, 0.9) !important;
|
||||
}
|
||||
|
||||
.nodedc-toolbar-filter-toggle[data-active="true"] {
|
||||
color: rgb(var(--nodedc-accent-rgb)) !important;
|
||||
}
|
||||
|
||||
.nodedc-filter-row-shell {
|
||||
background:
|
||||
linear-gradient(180deg, rgba(255, 255, 255, 0.03) 0%, rgba(255, 255, 255, 0.01) 100%),
|
||||
rgba(8, 8, 11, 0.84);
|
||||
border: 0 !important;
|
||||
border-radius: 1.35rem !important;
|
||||
-webkit-backdrop-filter: blur(20px);
|
||||
backdrop-filter: blur(20px);
|
||||
box-shadow: none !important;
|
||||
outline: none !important;
|
||||
}
|
||||
|
||||
.nodedc-top-toolbar-cluster > * {
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.nodedc-filter-chip {
|
||||
border: 0 !important;
|
||||
border-radius: 999px !important;
|
||||
background: rgba(255, 255, 255, 0.045) !important;
|
||||
color: var(--text-color-primary) !important;
|
||||
-webkit-backdrop-filter: blur(18px);
|
||||
backdrop-filter: blur(18px);
|
||||
box-shadow: none !important;
|
||||
outline: none !important;
|
||||
}
|
||||
|
||||
.nodedc-filter-chip-separator {
|
||||
border-right: 1px solid rgba(255, 255, 255, 0.08);
|
||||
}
|
||||
|
||||
.nodedc-filter-clear-button {
|
||||
border: 0 !important;
|
||||
outline: none !important;
|
||||
box-shadow: none !important;
|
||||
border-radius: 999px !important;
|
||||
background: rgba(255, 255, 255, 0.05) !important;
|
||||
color: var(--text-color-primary) !important;
|
||||
}
|
||||
|
||||
.nodedc-filter-clear-button:hover {
|
||||
background: rgba(255, 255, 255, 0.08) !important;
|
||||
}
|
||||
|
||||
.nodedc-calendar-shell {
|
||||
@apply rounded-[1.1rem] border-0 bg-transparent p-1 shadow-none outline-none;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -40,7 +40,7 @@ export function CustomSearchSelect(props: ICustomSearchSelectProps) {
|
|||
optionsClassName = "",
|
||||
value,
|
||||
tabIndex,
|
||||
noResultsMessage = "No matches found",
|
||||
noResultsMessage = "Совпадений не найдено",
|
||||
defaultOpen = false,
|
||||
} = props;
|
||||
const [query, setQuery] = useState("");
|
||||
|
|
@ -104,14 +104,14 @@ export function CustomSearchSelect(props: ICustomSearchSelectProps) {
|
|||
<button
|
||||
ref={setReferenceElement}
|
||||
type="button"
|
||||
className={cn(
|
||||
"flex w-full items-center justify-between gap-1 text-11",
|
||||
{
|
||||
"cursor-not-allowed text-secondary": disabled,
|
||||
"cursor-pointer hover:bg-layer-transparent-hover": !disabled,
|
||||
},
|
||||
customButtonClassName
|
||||
)}
|
||||
className={cn(
|
||||
"flex w-full items-center justify-between gap-1 text-11",
|
||||
{
|
||||
"cursor-not-allowed text-secondary": disabled,
|
||||
"cursor-pointer": !disabled,
|
||||
},
|
||||
customButtonClassName
|
||||
)}
|
||||
onClick={toggleDropdown}
|
||||
>
|
||||
{customButton}
|
||||
|
|
@ -122,13 +122,13 @@ export function CustomSearchSelect(props: ICustomSearchSelectProps) {
|
|||
<button
|
||||
ref={setReferenceElement}
|
||||
type="button"
|
||||
className={cn(
|
||||
"flex w-full items-center justify-between gap-1 rounded-sm border-[0.5px] border-strong",
|
||||
className={cn(
|
||||
"flex w-full items-center justify-between gap-1 rounded-full border-0 outline-none",
|
||||
{
|
||||
"px-3 py-2 text-13": input,
|
||||
"px-2 py-1 text-11": !input,
|
||||
"cursor-not-allowed text-secondary": disabled,
|
||||
"cursor-pointer hover:bg-layer-transparent-hover": !disabled,
|
||||
"cursor-pointer": !disabled,
|
||||
},
|
||||
buttonClassName
|
||||
)}
|
||||
|
|
@ -146,25 +146,25 @@ export function CustomSearchSelect(props: ICustomSearchSelectProps) {
|
|||
<Combobox.Options data-prevent-outside-click static>
|
||||
<div
|
||||
className={cn(
|
||||
"z-30 my-1 min-w-48 overflow-y-scroll rounded-md border-[0.5px] border-subtle-1 bg-surface-1 py-2.5 text-11 whitespace-nowrap focus:outline-none",
|
||||
"nodedc-dropdown-surface z-30 my-1 min-w-48 overflow-y-scroll whitespace-nowrap focus:outline-none",
|
||||
optionsClassName
|
||||
)}
|
||||
ref={setPopperElement}
|
||||
style={styles.popper}
|
||||
{...attributes.popper}
|
||||
>
|
||||
<div className="mx-2 flex items-center gap-1.5 rounded-sm border border-subtle px-2">
|
||||
<div className="nodedc-dropdown-search mx-1">
|
||||
<SearchIcon className="h-3.5 w-3.5 text-placeholder" strokeWidth={1.5} />
|
||||
<Combobox.Input
|
||||
className="w-full bg-transparent py-1 text-11 text-secondary placeholder:text-placeholder focus:outline-none"
|
||||
value={query}
|
||||
onChange={(e) => setQuery(e.target.value)}
|
||||
placeholder="Search"
|
||||
placeholder="Поиск"
|
||||
displayValue={(assigned: any) => assigned?.name}
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
className={cn("vertical-scrollbar mt-2 scrollbar-xs space-y-1 overflow-y-scroll px-2", {
|
||||
className={cn("vertical-scrollbar mt-2 scrollbar-xs space-y-1 overflow-y-scroll px-1", {
|
||||
"max-h-96": maxHeight === "2xl",
|
||||
"max-h-80": maxHeight === "xl",
|
||||
"max-h-60": maxHeight === "lg",
|
||||
|
|
@ -181,9 +181,9 @@ export function CustomSearchSelect(props: ICustomSearchSelectProps) {
|
|||
value={option.value}
|
||||
className={({ active }) =>
|
||||
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,
|
||||
"cursor-not-allowed text-placeholder opacity-60": option.disabled,
|
||||
}
|
||||
)
|
||||
|
|
|
|||
Loading…
Reference in New Issue