UI - МЕЖПРОЕКТНАЯ КОММУНИКАЦИЯ: поиск из лупы, маршрутизация и статусные dropdown
This commit is contained in:
parent
21581373cd
commit
16f1552b22
|
|
@ -7,7 +7,6 @@
|
|||
import type { ReactNode } from "react";
|
||||
import { observer } from "mobx-react";
|
||||
import { useTranslation } from "@plane/i18n";
|
||||
import { Badge } from "@plane/propel/badge";
|
||||
import type { TExternalContourRequest } from "@plane/types";
|
||||
import { Avatar } from "@plane/ui";
|
||||
import { renderFormattedDate } from "@plane/utils";
|
||||
|
|
@ -54,7 +53,7 @@ export const ExternalContoursRequestTraceability = observer(function ExternalCon
|
|||
</TraceabilityCell>
|
||||
|
||||
<TraceabilityCell label={t("external_contours_page.traceability.target_contour")}>
|
||||
<Badge variant="neutral">{targetProjectName}</Badge>
|
||||
{targetProjectName}
|
||||
</TraceabilityCell>
|
||||
|
||||
<TraceabilityCell label={t("external_contours_page.traceability.status")}>
|
||||
|
|
@ -72,7 +71,7 @@ export const ExternalContoursRequestTraceability = observer(function ExternalCon
|
|||
{assigneeDetails.length > 0 ? (
|
||||
<div className="flex flex-wrap items-center gap-2">
|
||||
{assigneeDetails.map((assignee) => (
|
||||
<div key={assignee.id} className="flex items-center gap-2 rounded-sm border border-strong px-2 py-1">
|
||||
<div key={assignee.id} className="flex items-center gap-2">
|
||||
<Avatar src={assignee.avatar_url || ""} name={assignee.display_name || t("common.none")} size="sm" showTooltip />
|
||||
<span className="text-12 font-medium text-secondary">{assignee.display_name}</span>
|
||||
</div>
|
||||
|
|
@ -91,13 +90,13 @@ export const ExternalContoursRequestTraceability = observer(function ExternalCon
|
|||
{requestedAt ? renderFormattedDate(requestedAt) : t("common.none")}
|
||||
</TraceabilityCell>
|
||||
|
||||
<TraceabilityCell label={t("external_contours_page.traceability.due_date")}>
|
||||
{dueDate}
|
||||
</TraceabilityCell>
|
||||
|
||||
<TraceabilityCell label={t("external_contours_page.traceability.last_updated")}>
|
||||
{lastUpdatedAt ? renderFormattedDate(lastUpdatedAt) : t("common.none")}
|
||||
</TraceabilityCell>
|
||||
|
||||
<TraceabilityCell label={t("external_contours_page.traceability.due_date")}>
|
||||
{dueDate}
|
||||
</TraceabilityCell>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -19,7 +19,7 @@ export function ExternalContourStatePill(props: Props) {
|
|||
const state = request.issue.state_detail;
|
||||
|
||||
return (
|
||||
<div className="inline-flex items-center gap-1 rounded-sm border border-subtle bg-layer-2 px-1.5 py-0.5 text-11 font-medium text-secondary">
|
||||
<div className="inline-flex items-center gap-1.5 text-13 font-medium text-secondary">
|
||||
<StateGroupIcon
|
||||
stateGroup={state?.group ?? "backlog"}
|
||||
color={state?.color}
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ import { usePopper } from "react-popper";
|
|||
import { Combobox } from "@headlessui/react";
|
||||
// plane imports
|
||||
import { useTranslation } from "@plane/i18n";
|
||||
import { SearchIcon, IntakeStateGroupIcon, ChevronDownIcon } from "@plane/propel/icons";
|
||||
import { SearchIcon, IntakeStateGroupIcon, ChevronDownIcon, CheckIcon } from "@plane/propel/icons";
|
||||
import type { IIntakeState } from "@plane/types";
|
||||
import { ComboDropDown, Spinner } from "@plane/ui";
|
||||
import { cn } from "@plane/utils";
|
||||
|
|
@ -21,9 +21,6 @@ import { BUTTON_VARIANTS_WITH_TEXT } from "@/components/dropdowns/constants";
|
|||
import type { TDropdownProps } from "@/components/dropdowns/types";
|
||||
// hooks
|
||||
import { useDropdown } from "@/hooks/use-dropdown";
|
||||
// plane web imports
|
||||
import { StateOption } from "@/plane-web/components/workflow";
|
||||
|
||||
export type TWorkItemStateDropdownBaseProps = TDropdownProps & {
|
||||
alwaysAllowStateChange?: boolean;
|
||||
button?: ReactNode;
|
||||
|
|
@ -135,70 +132,69 @@ export const WorkItemStateDropdownBase = observer(function WorkItemStateDropdown
|
|||
handleClose();
|
||||
};
|
||||
|
||||
const comboButton = (
|
||||
<>
|
||||
{button ? (
|
||||
<button
|
||||
ref={setReferenceElement}
|
||||
type="button"
|
||||
className={cn("clickable block h-full w-full outline-none", buttonContainerClassName)}
|
||||
onClick={handleOnClick}
|
||||
disabled={disabled}
|
||||
tabIndex={tabIndex}
|
||||
>
|
||||
{button}
|
||||
</button>
|
||||
) : (
|
||||
<button
|
||||
tabIndex={tabIndex}
|
||||
ref={setReferenceElement}
|
||||
type="button"
|
||||
className={cn(
|
||||
"clickable block h-full max-w-full outline-none",
|
||||
{
|
||||
"cursor-not-allowed text-secondary": disabled,
|
||||
"cursor-pointer": !disabled,
|
||||
},
|
||||
buttonContainerClassName
|
||||
)}
|
||||
onClick={handleOnClick}
|
||||
disabled={disabled}
|
||||
>
|
||||
<DropdownButton
|
||||
className={buttonClassName}
|
||||
isActive={isOpen}
|
||||
tooltipHeading={t("state")}
|
||||
tooltipContent={selectedState?.name ?? t("state")}
|
||||
showTooltip={showTooltip}
|
||||
variant={buttonVariant}
|
||||
renderToolTipByDefault={renderByDefault}
|
||||
>
|
||||
{isInitializing ? (
|
||||
<Spinner className="h-3.5 w-3.5" />
|
||||
) : (
|
||||
<>
|
||||
{!hideIcon && (
|
||||
<IntakeStateGroupIcon
|
||||
stateGroup={selectedState?.group ?? "triage"}
|
||||
color={selectedState?.color ?? "var(--text-color-tertiary)"}
|
||||
className={cn("flex-shrink-0", iconSize)}
|
||||
/>
|
||||
)}
|
||||
{BUTTON_VARIANTS_WITH_TEXT.includes(buttonVariant) && (
|
||||
<span className="flex-grow truncate text-left">{selectedState?.name ?? t("state")}</span>
|
||||
)}
|
||||
{dropdownArrow && (
|
||||
<ChevronDownIcon
|
||||
className={cn("h-2.5 w-2.5 flex-shrink-0", dropdownArrowClassName)}
|
||||
aria-hidden="true"
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</DropdownButton>
|
||||
</button>
|
||||
const comboButton = button ? (
|
||||
<button
|
||||
ref={setReferenceElement}
|
||||
type="button"
|
||||
className={cn(
|
||||
"clickable block h-full w-full rounded-full border-0 bg-transparent shadow-none outline-none focus:outline-none focus-visible:outline-none focus-visible:ring-0",
|
||||
buttonContainerClassName
|
||||
)}
|
||||
</>
|
||||
onClick={handleOnClick}
|
||||
disabled={disabled}
|
||||
tabIndex={tabIndex}
|
||||
>
|
||||
{button}
|
||||
</button>
|
||||
) : (
|
||||
<button
|
||||
tabIndex={tabIndex}
|
||||
ref={setReferenceElement}
|
||||
type="button"
|
||||
className={cn(
|
||||
"clickable block h-full max-w-full rounded-full border-0 bg-transparent shadow-none outline-none focus:outline-none focus-visible:outline-none focus-visible:ring-0",
|
||||
{
|
||||
"cursor-not-allowed text-secondary": disabled,
|
||||
"cursor-pointer": !disabled,
|
||||
},
|
||||
buttonContainerClassName
|
||||
)}
|
||||
onClick={handleOnClick}
|
||||
disabled={disabled}
|
||||
>
|
||||
<DropdownButton
|
||||
className={buttonClassName}
|
||||
isActive={isOpen}
|
||||
tooltipHeading={t("state")}
|
||||
tooltipContent={selectedState?.name ?? t("state")}
|
||||
showTooltip={showTooltip}
|
||||
variant={buttonVariant}
|
||||
renderToolTipByDefault={renderByDefault}
|
||||
>
|
||||
{isInitializing ? (
|
||||
<Spinner className="h-3.5 w-3.5" />
|
||||
) : (
|
||||
<>
|
||||
{!hideIcon && (
|
||||
<IntakeStateGroupIcon
|
||||
stateGroup={selectedState?.group ?? "triage"}
|
||||
color={selectedState?.color ?? "var(--text-color-tertiary)"}
|
||||
className={cn("flex-shrink-0", iconSize)}
|
||||
/>
|
||||
)}
|
||||
{BUTTON_VARIANTS_WITH_TEXT.includes(buttonVariant) && (
|
||||
<span className="flex-grow truncate text-left">{selectedState?.name ?? t("state")}</span>
|
||||
)}
|
||||
{dropdownArrow && (
|
||||
<ChevronDownIcon
|
||||
className={cn("h-2.5 w-2.5 flex-shrink-0", dropdownArrowClassName)}
|
||||
aria-hidden="true"
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</DropdownButton>
|
||||
</button>
|
||||
);
|
||||
|
||||
return (
|
||||
|
|
@ -216,17 +212,17 @@ export const WorkItemStateDropdownBase = observer(function WorkItemStateDropdown
|
|||
{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-glass-modal nodedc-glass-surface my-1 w-52 rounded-[1.25rem] border-0 px-3 py-3 text-12 shadow-none outline-none"
|
||||
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="flex items-center gap-1.5 rounded-[0.95rem] border-0 bg-white/5 px-3 py-2 outline-none">
|
||||
<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 outline-none focus:outline-none"
|
||||
value={query}
|
||||
onChange={(e) => setQuery(e.target.value)}
|
||||
placeholder={t("common.search.label")}
|
||||
|
|
@ -234,17 +230,28 @@ export const WorkItemStateDropdownBase = observer(function WorkItemStateDropdown
|
|||
onKeyDown={searchInputKeyDown}
|
||||
/>
|
||||
</div>
|
||||
<div className="mt-2 max-h-48 space-y-1 overflow-y-scroll">
|
||||
<div className="mt-2 max-h-56 space-y-1 overflow-y-auto">
|
||||
{filteredOptions ? (
|
||||
filteredOptions.length > 0 ? (
|
||||
filteredOptions.map((option) => (
|
||||
<StateOption
|
||||
{...props}
|
||||
<Combobox.Option
|
||||
key={option.value}
|
||||
option={option}
|
||||
selectedValue={value}
|
||||
className="flex w-full cursor-pointer items-center justify-between gap-2 truncate rounded-sm px-1 py-1.5 select-none"
|
||||
/>
|
||||
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"}`
|
||||
)
|
||||
}
|
||||
>
|
||||
{({ selected }) => (
|
||||
<>
|
||||
<span className="flex-grow truncate">{option.content}</span>
|
||||
{selected && <CheckIcon className="h-3.5 w-3.5 flex-shrink-0" />}
|
||||
</>
|
||||
)}
|
||||
</Combobox.Option>
|
||||
))
|
||||
) : (
|
||||
<p className="px-1.5 py-1 text-placeholder italic">{t("no_matching_results")}</p>
|
||||
|
|
|
|||
|
|
@ -102,26 +102,18 @@ function BorderButton(props: ButtonProps) {
|
|||
>
|
||||
{!hideIcon &&
|
||||
(priority ? (
|
||||
<div
|
||||
className={cn({
|
||||
// highlight just the icon if text is visible and priority is urgent
|
||||
"rounded-sm border border-priority-urgent p-0.5": priority === "urgent" && !hideText && highlightUrgent,
|
||||
<PriorityIcon
|
||||
priority={priority}
|
||||
size={12}
|
||||
className={cn("flex-shrink-0", {
|
||||
// increase the icon size if text is hidden
|
||||
"h-3.5 w-3.5": hideText,
|
||||
// centre align the icons if text is hidden
|
||||
"translate-x-[0.0625rem]": hideText && priority === "high",
|
||||
"translate-x-0.5": hideText && priority === "medium",
|
||||
"translate-x-1": hideText && priority === "low",
|
||||
})}
|
||||
>
|
||||
<PriorityIcon
|
||||
priority={priority}
|
||||
size={12}
|
||||
className={cn("flex-shrink-0", {
|
||||
// increase the icon size if text is hidden
|
||||
"h-3.5 w-3.5": hideText,
|
||||
// centre align the icons if text is hidden
|
||||
"translate-x-[0.0625rem]": hideText && priority === "high",
|
||||
"translate-x-0.5": hideText && priority === "medium",
|
||||
"translate-x-1": hideText && priority === "low",
|
||||
// highlight the icon if priority is urgent
|
||||
})}
|
||||
/>
|
||||
</div>
|
||||
/>
|
||||
) : (
|
||||
<SignalHigh className="size-3" />
|
||||
))}
|
||||
|
|
@ -132,7 +124,7 @@ function BorderButton(props: ButtonProps) {
|
|||
"text-placeholder": !priority || priority === "none",
|
||||
})}
|
||||
>
|
||||
{priorityDetails?.title ?? placeholder}
|
||||
{priority ? t(priority) : placeholder}
|
||||
</span>
|
||||
)}
|
||||
{dropdownArrow && (
|
||||
|
|
@ -193,26 +185,18 @@ function BackgroundButton(props: ButtonProps) {
|
|||
>
|
||||
{!hideIcon &&
|
||||
(priority ? (
|
||||
<div
|
||||
className={cn({
|
||||
// highlight just the icon if text is visible and priority is urgent
|
||||
"rounded-sm border border-priority-urgent p-0.5": priority === "urgent" && !hideText && highlightUrgent,
|
||||
<PriorityIcon
|
||||
priority={priority}
|
||||
size={12}
|
||||
className={cn("flex-shrink-0", {
|
||||
// increase the icon size if text is hidden
|
||||
"h-3.5 w-3.5": hideText,
|
||||
// centre align the icons if text is hidden
|
||||
"translate-x-[0.0625rem]": hideText && priority === "high",
|
||||
"translate-x-0.5": hideText && priority === "medium",
|
||||
"translate-x-1": hideText && priority === "low",
|
||||
})}
|
||||
>
|
||||
<PriorityIcon
|
||||
priority={priority}
|
||||
size={12}
|
||||
className={cn("flex-shrink-0", {
|
||||
// increase the icon size if text is hidden
|
||||
"h-3.5 w-3.5": hideText,
|
||||
// centre align the icons if text is hidden
|
||||
"translate-x-[0.0625rem]": hideText && priority === "high",
|
||||
"translate-x-0.5": hideText && priority === "medium",
|
||||
"translate-x-1": hideText && priority === "low",
|
||||
// highlight the icon if priority is urgent
|
||||
})}
|
||||
/>
|
||||
</div>
|
||||
/>
|
||||
) : (
|
||||
<SignalHigh className="size-3" />
|
||||
))}
|
||||
|
|
@ -223,7 +207,7 @@ function BackgroundButton(props: ButtonProps) {
|
|||
"text-placeholder": !priority || priority === "none",
|
||||
})}
|
||||
>
|
||||
{priorityDetails?.title ?? t("common.priority") ?? placeholder}
|
||||
{priority ? t(priority) : t("common.priority") ?? placeholder}
|
||||
</span>
|
||||
)}
|
||||
{dropdownArrow && (
|
||||
|
|
@ -277,26 +261,18 @@ function TransparentButton(props: ButtonProps) {
|
|||
>
|
||||
{!hideIcon &&
|
||||
(priority ? (
|
||||
<div
|
||||
className={cn({
|
||||
// highlight just the icon if text is visible and priority is urgent
|
||||
"rounded-sm border border-priority-urgent p-0.5": priority === "urgent" && !hideText && highlightUrgent,
|
||||
<PriorityIcon
|
||||
priority={priority}
|
||||
size={12}
|
||||
className={cn("flex-shrink-0", {
|
||||
// increase the icon size if text is hidden
|
||||
"h-3.5 w-3.5": hideText,
|
||||
// centre align the icons if text is hidden
|
||||
"translate-x-[0.0625rem]": hideText && priority === "high",
|
||||
"translate-x-0.5": hideText && priority === "medium",
|
||||
"translate-x-1": hideText && priority === "low",
|
||||
})}
|
||||
>
|
||||
<PriorityIcon
|
||||
priority={priority}
|
||||
size={12}
|
||||
className={cn("flex-shrink-0", {
|
||||
// increase the icon size if text is hidden
|
||||
"h-3.5 w-3.5": hideText,
|
||||
// centre align the icons if text is hidden
|
||||
"translate-x-[0.0625rem]": hideText && priority === "high",
|
||||
"translate-x-0.5": hideText && priority === "medium",
|
||||
"translate-x-1": hideText && priority === "low",
|
||||
// highlight the icon if priority is urgent
|
||||
})}
|
||||
/>
|
||||
</div>
|
||||
/>
|
||||
) : (
|
||||
<SignalHigh className="size-3" />
|
||||
))}
|
||||
|
|
@ -307,7 +283,7 @@ function TransparentButton(props: ButtonProps) {
|
|||
"text-placeholder": !priority || priority === "none",
|
||||
})}
|
||||
>
|
||||
{priorityDetails?.title ?? t("common.priority") ?? placeholder}
|
||||
{priority ? t(priority) : t("common.priority") ?? placeholder}
|
||||
</span>
|
||||
)}
|
||||
{dropdownArrow && (
|
||||
|
|
@ -368,8 +344,8 @@ export function PriorityDropdown(props: Props) {
|
|||
query: priority.key,
|
||||
content: (
|
||||
<div className="flex items-center gap-2">
|
||||
<PriorityIcon priority={priority.key} size={14} withContainer />
|
||||
<span className="flex-grow truncate">{priority.title}</span>
|
||||
<PriorityIcon priority={priority.key} size={14} />
|
||||
<span className="flex-grow truncate">{t(priority.key)}</span>
|
||||
</div>
|
||||
),
|
||||
}));
|
||||
|
|
@ -398,63 +374,53 @@ export function PriorityDropdown(props: Props) {
|
|||
? BackgroundButton
|
||||
: TransparentButton;
|
||||
|
||||
const comboButton = (
|
||||
<>
|
||||
{button ? (
|
||||
<button
|
||||
ref={setReferenceElement}
|
||||
type="button"
|
||||
className={cn("clickable block h-full w-full outline-none", buttonContainerClassName)}
|
||||
onClick={handleOnClick}
|
||||
disabled={disabled}
|
||||
tabIndex={tabIndex}
|
||||
>
|
||||
{button}
|
||||
</button>
|
||||
) : (
|
||||
<button
|
||||
ref={setReferenceElement}
|
||||
type="button"
|
||||
className={cn(
|
||||
"clickable block h-full max-w-full outline-none",
|
||||
{
|
||||
"cursor-not-allowed text-secondary": disabled,
|
||||
"cursor-pointer": !disabled,
|
||||
},
|
||||
buttonContainerClassName
|
||||
)}
|
||||
onClick={handleOnClick}
|
||||
disabled={disabled}
|
||||
tabIndex={tabIndex}
|
||||
>
|
||||
<ButtonToRender
|
||||
priority={value ?? undefined}
|
||||
className={buttonClassName}
|
||||
highlightUrgent={highlightUrgent}
|
||||
dropdownArrow={dropdownArrow && !disabled}
|
||||
dropdownArrowClassName={dropdownArrowClassName}
|
||||
hideIcon={hideIcon}
|
||||
placeholder={placeholder}
|
||||
showTooltip={showTooltip}
|
||||
hideText={BUTTON_VARIANTS_WITHOUT_TEXT.includes(buttonVariant)}
|
||||
renderToolTipByDefault={renderByDefault}
|
||||
/>
|
||||
</button>
|
||||
const comboButton = button ? (
|
||||
<button
|
||||
ref={setReferenceElement}
|
||||
type="button"
|
||||
className={cn("clickable block h-full w-full outline-none", buttonContainerClassName)}
|
||||
onClick={handleOnClick}
|
||||
disabled={disabled}
|
||||
tabIndex={tabIndex}
|
||||
>
|
||||
{button}
|
||||
</button>
|
||||
) : (
|
||||
<button
|
||||
ref={setReferenceElement}
|
||||
type="button"
|
||||
className={cn(
|
||||
"clickable block h-full max-w-full outline-none",
|
||||
{
|
||||
"cursor-not-allowed text-secondary": disabled,
|
||||
"cursor-pointer": !disabled,
|
||||
},
|
||||
buttonContainerClassName
|
||||
)}
|
||||
</>
|
||||
onClick={handleOnClick}
|
||||
disabled={disabled}
|
||||
tabIndex={tabIndex}
|
||||
>
|
||||
<ButtonToRender
|
||||
priority={value ?? undefined}
|
||||
className={buttonClassName}
|
||||
highlightUrgent={highlightUrgent}
|
||||
dropdownArrow={dropdownArrow && !disabled}
|
||||
dropdownArrowClassName={dropdownArrowClassName}
|
||||
hideIcon={hideIcon}
|
||||
placeholder={placeholder}
|
||||
showTooltip={showTooltip}
|
||||
hideText={BUTTON_VARIANTS_WITHOUT_TEXT.includes(buttonVariant)}
|
||||
renderToolTipByDefault={renderByDefault}
|
||||
/>
|
||||
</button>
|
||||
);
|
||||
|
||||
return (
|
||||
<ComboDropDown
|
||||
as="div"
|
||||
ref={dropdownRef}
|
||||
className={cn(
|
||||
"h-full",
|
||||
{
|
||||
"bg-layer-1": isOpen,
|
||||
},
|
||||
className
|
||||
)}
|
||||
className={cn("h-full", className)}
|
||||
value={value}
|
||||
onChange={dropdownOnChange}
|
||||
disabled={disabled}
|
||||
|
|
@ -465,17 +431,17 @@ export function PriorityDropdown(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-glass-modal nodedc-glass-surface my-1 w-52 rounded-[1.25rem] border-0 px-3 py-3 text-12 shadow-none outline-none"
|
||||
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="flex items-center gap-1.5 rounded-[0.95rem] border-0 bg-white/5 px-3 py-2 outline-none">
|
||||
<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 outline-none focus:outline-none"
|
||||
value={query}
|
||||
onChange={(e) => setQuery(e.target.value)}
|
||||
placeholder={t("search")}
|
||||
|
|
@ -483,7 +449,7 @@ export function PriorityDropdown(props: Props) {
|
|||
onKeyDown={searchInputKeyDown}
|
||||
/>
|
||||
</div>
|
||||
<div className="mt-2 max-h-48 space-y-1 overflow-y-scroll">
|
||||
<div className="mt-2 max-h-56 space-y-1 overflow-y-auto">
|
||||
{filteredOptions.length > 0 ? (
|
||||
filteredOptions.map((option) => (
|
||||
<Combobox.Option
|
||||
|
|
@ -491,8 +457,8 @@ export function PriorityDropdown(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 ${
|
||||
active ? "bg-layer-transparent-hover" : ""
|
||||
`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"}`
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ import { usePopper } from "react-popper";
|
|||
import { Combobox } from "@headlessui/react";
|
||||
// plane imports
|
||||
import { useTranslation } from "@plane/i18n";
|
||||
import { SearchIcon, StateGroupIcon, ChevronDownIcon } from "@plane/propel/icons";
|
||||
import { SearchIcon, StateGroupIcon, ChevronDownIcon, CheckIcon } from "@plane/propel/icons";
|
||||
import type { IState } from "@plane/types";
|
||||
import { ComboDropDown, Spinner } from "@plane/ui";
|
||||
import { cn } from "@plane/utils";
|
||||
|
|
@ -21,9 +21,6 @@ import { BUTTON_VARIANTS_WITH_TEXT } from "@/components/dropdowns/constants";
|
|||
import type { TDropdownProps } from "@/components/dropdowns/types";
|
||||
// hooks
|
||||
import { useDropdown } from "@/hooks/use-dropdown";
|
||||
// plane web imports
|
||||
import { StateOption } from "@/plane-web/components/workflow";
|
||||
|
||||
export type TWorkItemStateDropdownBaseProps = TDropdownProps & {
|
||||
alwaysAllowStateChange?: boolean;
|
||||
button?: ReactNode;
|
||||
|
|
@ -136,71 +133,70 @@ export const WorkItemStateDropdownBase = observer(function WorkItemStateDropdown
|
|||
handleClose();
|
||||
};
|
||||
|
||||
const comboButton = (
|
||||
<>
|
||||
{button ? (
|
||||
<button
|
||||
ref={setReferenceElement}
|
||||
type="button"
|
||||
className={cn("clickable block h-full w-full outline-none", buttonContainerClassName)}
|
||||
onClick={handleOnClick}
|
||||
disabled={disabled}
|
||||
tabIndex={tabIndex}
|
||||
>
|
||||
{button}
|
||||
</button>
|
||||
) : (
|
||||
<button
|
||||
tabIndex={tabIndex}
|
||||
ref={setReferenceElement}
|
||||
type="button"
|
||||
className={cn(
|
||||
"clickable block h-full max-w-full outline-none",
|
||||
{
|
||||
"cursor-not-allowed text-secondary": disabled,
|
||||
"cursor-pointer": !disabled,
|
||||
},
|
||||
buttonContainerClassName
|
||||
)}
|
||||
onClick={handleOnClick}
|
||||
disabled={disabled}
|
||||
>
|
||||
<DropdownButton
|
||||
className={buttonClassName}
|
||||
isActive={isOpen}
|
||||
tooltipHeading={t("state")}
|
||||
tooltipContent={selectedState?.name ?? t("state")}
|
||||
showTooltip={showTooltip}
|
||||
variant={buttonVariant}
|
||||
renderToolTipByDefault={renderByDefault}
|
||||
>
|
||||
{isInitializing ? (
|
||||
<Spinner className="h-3.5 w-3.5" />
|
||||
) : (
|
||||
<>
|
||||
{!hideIcon && (
|
||||
<StateGroupIcon
|
||||
stateGroup={selectedState?.group ?? "backlog"}
|
||||
color={selectedState?.color ?? "var(--text-color-tertiary)"}
|
||||
className={cn("flex-shrink-0", iconSize)}
|
||||
percentage={selectedState?.order}
|
||||
/>
|
||||
)}
|
||||
{BUTTON_VARIANTS_WITH_TEXT.includes(buttonVariant) && (
|
||||
<span className="flex-grow truncate text-left">{selectedState?.name ?? t("state")}</span>
|
||||
)}
|
||||
{dropdownArrow && (
|
||||
<ChevronDownIcon
|
||||
className={cn("h-2.5 w-2.5 flex-shrink-0", dropdownArrowClassName)}
|
||||
aria-hidden="true"
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</DropdownButton>
|
||||
</button>
|
||||
const comboButton = button ? (
|
||||
<button
|
||||
ref={setReferenceElement}
|
||||
type="button"
|
||||
className={cn(
|
||||
"clickable block h-full w-full rounded-full border-0 bg-transparent shadow-none outline-none focus:outline-none focus-visible:outline-none focus-visible:ring-0",
|
||||
buttonContainerClassName
|
||||
)}
|
||||
</>
|
||||
onClick={handleOnClick}
|
||||
disabled={disabled}
|
||||
tabIndex={tabIndex}
|
||||
>
|
||||
{button}
|
||||
</button>
|
||||
) : (
|
||||
<button
|
||||
tabIndex={tabIndex}
|
||||
ref={setReferenceElement}
|
||||
type="button"
|
||||
className={cn(
|
||||
"clickable block h-full max-w-full rounded-full border-0 bg-transparent shadow-none outline-none focus:outline-none focus-visible:outline-none focus-visible:ring-0",
|
||||
{
|
||||
"cursor-not-allowed text-secondary": disabled,
|
||||
"cursor-pointer": !disabled,
|
||||
},
|
||||
buttonContainerClassName
|
||||
)}
|
||||
onClick={handleOnClick}
|
||||
disabled={disabled}
|
||||
>
|
||||
<DropdownButton
|
||||
className={buttonClassName}
|
||||
isActive={isOpen}
|
||||
tooltipHeading={t("state")}
|
||||
tooltipContent={selectedState?.name ?? t("state")}
|
||||
showTooltip={showTooltip}
|
||||
variant={buttonVariant}
|
||||
renderToolTipByDefault={renderByDefault}
|
||||
>
|
||||
{isInitializing ? (
|
||||
<Spinner className="h-3.5 w-3.5" />
|
||||
) : (
|
||||
<>
|
||||
{!hideIcon && (
|
||||
<StateGroupIcon
|
||||
stateGroup={selectedState?.group ?? "backlog"}
|
||||
color={selectedState?.color ?? "var(--text-color-tertiary)"}
|
||||
className={cn("flex-shrink-0", iconSize)}
|
||||
percentage={selectedState?.order}
|
||||
/>
|
||||
)}
|
||||
{BUTTON_VARIANTS_WITH_TEXT.includes(buttonVariant) && (
|
||||
<span className="flex-grow truncate text-left">{selectedState?.name ?? t("state")}</span>
|
||||
)}
|
||||
{dropdownArrow && (
|
||||
<ChevronDownIcon
|
||||
className={cn("h-2.5 w-2.5 flex-shrink-0", dropdownArrowClassName)}
|
||||
aria-hidden="true"
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</DropdownButton>
|
||||
</button>
|
||||
);
|
||||
|
||||
return (
|
||||
|
|
@ -218,17 +214,17 @@ export const WorkItemStateDropdownBase = observer(function WorkItemStateDropdown
|
|||
{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-glass-modal nodedc-glass-surface my-1 w-52 rounded-[1.25rem] border-0 px-3 py-3 text-12 shadow-none outline-none"
|
||||
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="flex items-center gap-1.5 rounded-[0.95rem] border-0 bg-white/5 px-3 py-2 outline-none">
|
||||
<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 outline-none focus:outline-none"
|
||||
value={query}
|
||||
onChange={(e) => setQuery(e.target.value)}
|
||||
placeholder={t("common.search.label")}
|
||||
|
|
@ -236,17 +232,28 @@ export const WorkItemStateDropdownBase = observer(function WorkItemStateDropdown
|
|||
onKeyDown={searchInputKeyDown}
|
||||
/>
|
||||
</div>
|
||||
<div className="mt-2 max-h-48 space-y-1 overflow-y-scroll">
|
||||
<div className="mt-2 max-h-56 space-y-1 overflow-y-auto">
|
||||
{filteredOptions ? (
|
||||
filteredOptions.length > 0 ? (
|
||||
filteredOptions.map((option) => (
|
||||
<StateOption
|
||||
{...props}
|
||||
<Combobox.Option
|
||||
key={option.value}
|
||||
option={option}
|
||||
selectedValue={value}
|
||||
className="flex w-full cursor-pointer items-center justify-between gap-2 truncate rounded-sm px-1 py-1.5 select-none"
|
||||
/>
|
||||
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"}`
|
||||
)
|
||||
}
|
||||
>
|
||||
{({ selected }) => (
|
||||
<>
|
||||
<span className="flex-grow truncate">{option.content}</span>
|
||||
{selected && <CheckIcon className="h-3.5 w-3.5 flex-shrink-0" />}
|
||||
</>
|
||||
)}
|
||||
</Combobox.Option>
|
||||
))
|
||||
) : (
|
||||
<p className="px-1.5 py-1 text-placeholder italic">{t("no_matching_results")}</p>
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ import { Command } from "cmdk";
|
|||
import { observer } from "mobx-react";
|
||||
import { useParams } from "next/navigation";
|
||||
import { createPortal } from "react-dom";
|
||||
import { useTranslation } from "@plane/i18n";
|
||||
// hooks
|
||||
import { CloseIcon, SearchIcon } from "@plane/propel/icons";
|
||||
import { cn } from "@plane/utils";
|
||||
|
|
@ -28,6 +29,7 @@ type TTopNavPowerKProps = {
|
|||
|
||||
export const TopNavPowerK = observer((props: TTopNavPowerKProps) => {
|
||||
const { variant = "top-navigation" } = props;
|
||||
const { t } = useTranslation();
|
||||
// router
|
||||
const router = useAppRouter();
|
||||
const params = useParams();
|
||||
|
|
@ -313,7 +315,7 @@ export const TopNavPowerK = observer((props: TTopNavPowerKProps) => {
|
|||
onMouseDown={handleMouseDown}
|
||||
onFocus={handleFocus}
|
||||
onKeyDown={handleKeyDown}
|
||||
placeholder="Search commands..."
|
||||
placeholder={t("power_k.search_menu.quick_command_placeholder")}
|
||||
className="placeholder-text-placeholder min-w-0 flex-1 bg-transparent text-13 text-primary outline-none"
|
||||
/>
|
||||
{searchTerm && (
|
||||
|
|
@ -386,19 +388,24 @@ export const TopNavPowerK = observer((props: TTopNavPowerKProps) => {
|
|||
if (!isOpen) openPanel();
|
||||
}}
|
||||
onMouseDown={handleMouseDown}
|
||||
onFocus={handleFocus}
|
||||
onKeyDown={handleKeyDown}
|
||||
placeholder="Search commands..."
|
||||
className="placeholder-text-placeholder min-w-0 flex-1 bg-transparent text-13 text-primary outline-none"
|
||||
autoFocus
|
||||
/>
|
||||
onFocus={handleFocus}
|
||||
onKeyDown={handleKeyDown}
|
||||
placeholder={t("power_k.search_menu.quick_command_placeholder")}
|
||||
className="placeholder-text-placeholder min-w-0 flex-1 bg-transparent text-13 text-primary outline-none"
|
||||
autoFocus
|
||||
/>
|
||||
{searchTerm && (
|
||||
<button type="button" onClick={handleClear} className="ml-2 shrink-0">
|
||||
<CloseIcon className="size-3.5 text-placeholder hover:text-primary" />
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
<div className="nodedc-glass-modal nodedc-glass-surface mt-3 flex max-h-[70vh] w-full flex-col overflow-hidden rounded-[1.5rem] pt-3">
|
||||
<div className="nodedc-glass-modal nodedc-glass-surface absolute bottom-full left-0 mb-3 flex max-h-[70vh] w-full flex-col overflow-hidden rounded-[1.5rem] pt-3">
|
||||
<div className="px-4 pb-2">
|
||||
<div className="text-[13px] font-medium text-secondary">
|
||||
{t("power_k.search_menu.quick_access_title")}
|
||||
</div>
|
||||
</div>
|
||||
{searchCommandContent}
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -2891,6 +2891,8 @@ export default {
|
|||
search_menu: {
|
||||
no_results: "No results found",
|
||||
clear_search: "Clear search",
|
||||
quick_access_title: "Quick access",
|
||||
quick_command_placeholder: "Find a quick command",
|
||||
},
|
||||
footer: {
|
||||
workspace_level: "Workspace level",
|
||||
|
|
|
|||
|
|
@ -3044,6 +3044,8 @@ export default {
|
|||
search_menu: {
|
||||
no_results: "Ничего не найдено",
|
||||
clear_search: "Очистить поиск",
|
||||
quick_access_title: "Быстрый доступ",
|
||||
quick_command_placeholder: "Найти быструю команду",
|
||||
},
|
||||
footer: {
|
||||
workspace_level: "Уровень рабочего пространства",
|
||||
|
|
|
|||
Loading…
Reference in New Issue