Rework MemoDetailSidebar into a Linear-style rail: a quiet icon action cluster (pin, copy link, share image, share links) followed by label/value property rows for visibility, created/edited times, author, and location, then hairline-separated sections for tags, attachments, relations, and the outline. Attachments and reference relations were previously not surfaced in the sidebar at all. - Visibility is editable in place, reusing VisibilitySelector with a new compact size variant that matches the 13px row grammar - Pin toggle and visibility changes go through useUpdateMemo - MemoOutline gains active-section tracking (scroll-spy), depth indentation, and tick-rail styling; drops the animated underline and native title tooltips - Widen the detail rail from w-56 to w-60
54 lines
2.5 KiB
TypeScript
54 lines
2.5 KiB
TypeScript
import { CheckIcon, ChevronDownIcon } from "lucide-react";
|
|
import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger } from "@/components/ui/dropdown-menu";
|
|
import VisibilityIcon from "@/components/VisibilityIcon";
|
|
import { cn } from "@/lib/utils";
|
|
import { Visibility } from "@/types/proto/api/v1/memo_service_pb";
|
|
import { useTranslate } from "@/utils/i18n";
|
|
import type { VisibilitySelectorProps } from "../types";
|
|
|
|
const VisibilitySelector = (props: VisibilitySelectorProps) => {
|
|
const { value, onChange } = props;
|
|
const compact = props.size === "compact";
|
|
const t = useTranslate();
|
|
|
|
const visibilityOptions = [
|
|
{ value: Visibility.PRIVATE, label: t("memo.visibility.private"), description: t("memo.visibility.private-description") },
|
|
{ value: Visibility.PROTECTED, label: t("memo.visibility.protected"), description: t("memo.visibility.protected-description") },
|
|
{ value: Visibility.PUBLIC, label: t("memo.visibility.public"), description: t("memo.visibility.public-description") },
|
|
] as const;
|
|
|
|
const currentLabel = visibilityOptions.find((option) => option.value === value)?.label || "";
|
|
|
|
return (
|
|
<DropdownMenu onOpenChange={props.onOpenChange}>
|
|
<DropdownMenuTrigger
|
|
render={
|
|
<button
|
|
className={cn(
|
|
"inline-flex items-center rounded-md hover:bg-accent transition-colors",
|
|
compact ? "px-1.5 py-[3px] text-[13px] leading-5 text-foreground/85" : "h-8 px-2 text-sm text-muted-foreground",
|
|
)}
|
|
/>
|
|
}
|
|
>
|
|
<VisibilityIcon visibility={value} className={cn("opacity-60 mr-1.5", compact && "w-[13px]")} />
|
|
<span className="truncate">{currentLabel}</span>
|
|
<ChevronDownIcon className={cn("ml-0.5 opacity-60", compact ? "size-3.5 text-muted-foreground/70" : "w-4 h-4")} />
|
|
</DropdownMenuTrigger>
|
|
<DropdownMenuContent align="start">
|
|
{visibilityOptions.map((option) => (
|
|
<DropdownMenuItem key={option.value} onClick={() => onChange(option.value)}>
|
|
<VisibilityIcon visibility={option.value} />
|
|
<div className="flex flex-col">
|
|
<span>{option.label}</span>
|
|
<span className="text-xs text-muted-foreground">{option.description}</span>
|
|
</div>
|
|
{value === option.value && <CheckIcon className="ml-auto w-4 h-4 text-primary" />}
|
|
</DropdownMenuItem>
|
|
))}
|
|
</DropdownMenuContent>
|
|
</DropdownMenu>
|
|
);
|
|
};
|
|
|
|
export default VisibilitySelector;
|