mirror of
https://github.com/multica-ai/multica.git
synced 2026-07-26 12:35:35 +02:00
* feat(views): progressive disclosure for issue sidebar properties (MUL-2275) Split sidebar Properties into a core group that always renders (status / priority / assignee / labels) and an optional group (due_date / project / parent) that only appears when the issue has the value set or the user explicitly added it via a new "+ Add property" picker. A field cleared in-session stays visible to avoid row flicker; navigating to a different issue reseeds visibility from that issue's set fields. The standalone "Parent issue" card is folded into Properties as one of those optional rows. Adds `defaultOpen` to DueDatePicker / ProjectPicker so a newly-added row drops the user straight into edit state. Co-authored-by: multica-agent <github@multica.ai> * refactor(views): swap sidebar optional set to due_date + labels Per design feedback: status / priority / assignee / project / parent are all required and should always render in the sidebar; only due_date and labels are progressive-disclosure optionals. Move project and parent rows out of the optional block (drop their +Add property menu entries and the parent special-case in addOptionalProp). Move labels into the optional block, gated on the issue's actual attached- label count (queried via issueLabelsOptions), with defaultOpen wired through LabelPicker so picking "Labels" from +Add property drops the user straight into the picker. Tests updated for the new split. Co-authored-by: multica-agent <github@multica.ai> * refactor(views): restore standalone parent card, move priority to optional Parent goes back to its own collapsible section, rendered only when the issue actually has a parent — matching the pre-MUL-2275 behavior. It is no longer interleaved with Properties rows. Priority joins the progressive-disclosure set (priority / due_date / labels). New issues default to priority "none", so the row is hidden until set or added via "+ Add property", and PriorityPicker gains defaultOpen so the field drops straight into edit state when chosen from the add-property menu. Co-authored-by: multica-agent <github@multica.ai> * refactor(issue-detail): tighten Add-property popover visual rhythm Picked up a small visual inconsistency while reviewing the PR's UI: the "Add property" dropdown floated above the inspector at a noticeably larger type scale than the property rows, and each item was bare text while the rows it sat above all rendered with an icon + value pair. Tweaks: - Items: `text-sm py-1.5` → `text-xs py-1`, matching the inspector row typography and trimming row-to-row gap from 12px to 8px. - Each option leads with the icon the resulting picker uses (`PriorityIcon` bars / `CalendarDays` / `Tag`) so the dropdown reads as a preview of what will appear in the new PropRow. - Focus indicator: replace the default thick focus ring with `focus-visible:bg-accent + outline-none`, matching the hover state language — keyboard focus and mouse hover now look the same. - Popover width: `w-48` → `w-44` since the labels are short and the visual is now denser; still leaves room for translated strings. * fix(issue-detail): dismiss Add-property popover when an option is picked Base UI's `Popover` doesn't auto-dismiss when a child is clicked (it's not a Menu primitive), so picking an option left the "+ Add property" popover sitting behind the picker that auto-opens for the newly added row — two popovers visibly stacked. Make the Popover controlled with a local `addPropPopoverOpen` state and close it inside `addOptionalProp` right after enqueuing the row's auto-open. The picker still pops on mount via `defaultOpen={autoOpenProp === key}`, so the user flow is unchanged from their perspective: Click "+ Add property" → menu opens Click an option → menu closes AND target picker opens (Was the same flow on paper before; just had the orphan popover behind the picker.) --------- Co-authored-by: multica-agent <github@multica.ai>
73 lines
2.7 KiB
TypeScript
73 lines
2.7 KiB
TypeScript
"use client";
|
|
|
|
import { Check, FolderKanban, X } from "lucide-react";
|
|
import { useQuery } from "@tanstack/react-query";
|
|
import { projectListOptions } from "@multica/core/projects/queries";
|
|
import { useWorkspaceId } from "@multica/core/hooks";
|
|
import type { UpdateIssueRequest } from "@multica/core/types";
|
|
import {
|
|
DropdownMenu,
|
|
DropdownMenuContent,
|
|
DropdownMenuItem,
|
|
DropdownMenuTrigger,
|
|
DropdownMenuSeparator,
|
|
} from "@multica/ui/components/ui/dropdown-menu";
|
|
import { ProjectIcon } from "./project-icon";
|
|
import { useT } from "../../i18n";
|
|
|
|
export function ProjectPicker({
|
|
projectId,
|
|
onUpdate,
|
|
triggerRender,
|
|
align = "start",
|
|
defaultOpen = false,
|
|
}: {
|
|
projectId: string | null;
|
|
onUpdate: (updates: Partial<UpdateIssueRequest>) => void;
|
|
triggerRender?: React.ReactElement;
|
|
align?: "start" | "center" | "end";
|
|
/** Open the dropdown on first mount. Used by progressive-disclosure
|
|
* sidebars so a newly-added field immediately enters edit state. */
|
|
defaultOpen?: boolean;
|
|
}) {
|
|
const { t } = useT("projects");
|
|
const wsId = useWorkspaceId();
|
|
const { data: projects = [] } = useQuery(projectListOptions(wsId));
|
|
const current = projects.find((p) => p.id === projectId);
|
|
|
|
return (
|
|
<DropdownMenu defaultOpen={defaultOpen}>
|
|
<DropdownMenuTrigger
|
|
className={triggerRender ? undefined : "flex items-center gap-1.5 cursor-pointer rounded px-1 -mx-1 hover:bg-accent/30 transition-colors overflow-hidden"}
|
|
render={triggerRender}
|
|
>
|
|
{current ? (
|
|
<ProjectIcon project={current} size="sm" />
|
|
) : (
|
|
<FolderKanban className="h-3.5 w-3.5 shrink-0 text-muted-foreground" />
|
|
)}
|
|
<span className="truncate">{current ? current.title : t(($) => $.picker.no_project)}</span>
|
|
</DropdownMenuTrigger>
|
|
<DropdownMenuContent align={align} className="w-52">
|
|
{projects.map((p) => (
|
|
<DropdownMenuItem key={p.id} onClick={() => onUpdate({ project_id: p.id })}>
|
|
<ProjectIcon project={p} size="md" className="mr-1" />
|
|
<span className="truncate">{p.title}</span>
|
|
{p.id === projectId && <Check className="ml-auto h-3.5 w-3.5 shrink-0" />}
|
|
</DropdownMenuItem>
|
|
))}
|
|
{projects.length > 0 && projectId && <DropdownMenuSeparator />}
|
|
{projectId && (
|
|
<DropdownMenuItem onClick={() => onUpdate({ project_id: null })}>
|
|
<X className="h-3.5 w-3.5 text-muted-foreground" />
|
|
{t(($) => $.picker.remove)}
|
|
</DropdownMenuItem>
|
|
)}
|
|
{projects.length === 0 && (
|
|
<div className="px-2 py-1.5 text-xs text-muted-foreground">{t(($) => $.picker.empty)}</div>
|
|
)}
|
|
</DropdownMenuContent>
|
|
</DropdownMenu>
|
|
);
|
|
}
|