mirror of
https://github.com/colanode/colanode.git
synced 2025-12-16 11:47:47 +01:00
131 lines
4.3 KiB
TypeScript
131 lines
4.3 KiB
TypeScript
import { ChevronRight } from 'lucide-react';
|
|
import { RefAttributes, useRef } from 'react';
|
|
import { useDrop } from 'react-dnd';
|
|
import { toast } from 'sonner';
|
|
|
|
import { LocalSpaceNode } from '@colanode/client/types';
|
|
import { extractNodeRole } from '@colanode/core';
|
|
import { Avatar } from '@colanode/ui/components/avatars/avatar';
|
|
import { WorkspaceSidebarItem } from '@colanode/ui/components/layouts/sidebars/sidebar-item';
|
|
import { SpaceSidebarDropdown } from '@colanode/ui/components/spaces/space-sidebar-dropdown';
|
|
import {
|
|
Collapsible,
|
|
CollapsibleContent,
|
|
CollapsibleTrigger,
|
|
} from '@colanode/ui/components/ui/collapsible';
|
|
import { Link } from '@colanode/ui/components/ui/link';
|
|
import { useWorkspace } from '@colanode/ui/contexts/workspace';
|
|
import { useLiveQuery } from '@colanode/ui/hooks/use-live-query';
|
|
import { useMutation } from '@colanode/ui/hooks/use-mutation';
|
|
import { sortSpaceChildren } from '@colanode/ui/lib/spaces';
|
|
import { cn } from '@colanode/ui/lib/utils';
|
|
|
|
interface SpaceSidebarItemProps {
|
|
space: LocalSpaceNode;
|
|
}
|
|
|
|
export const SpaceSidebarItem = ({ space }: SpaceSidebarItemProps) => {
|
|
const workspace = useWorkspace();
|
|
const mutation = useMutation();
|
|
|
|
const role = extractNodeRole(space, workspace.userId);
|
|
const canEdit = role === 'admin';
|
|
|
|
const nodeChildrenGetQuery = useLiveQuery({
|
|
type: 'node.children.get',
|
|
nodeId: space.id,
|
|
userId: workspace.userId,
|
|
types: ['page', 'channel', 'database', 'folder'],
|
|
});
|
|
|
|
const [dropMonitor, dropRef] = useDrop({
|
|
accept: 'sidebar-item',
|
|
drop: () => ({
|
|
after: null,
|
|
}),
|
|
collect: (monitor) => ({
|
|
isOver: monitor.isOver(),
|
|
canDrop: monitor.canDrop(),
|
|
}),
|
|
});
|
|
|
|
const divRef = useRef<HTMLDivElement>(null);
|
|
const dropDivRef = dropRef(divRef);
|
|
|
|
const children = sortSpaceChildren(space, nodeChildrenGetQuery.data ?? []);
|
|
|
|
const handleDragEnd = (childId: string, after: string | null) => {
|
|
mutation.mutate({
|
|
input: {
|
|
type: 'space.child.reorder',
|
|
userId: workspace.userId,
|
|
spaceId: space.id,
|
|
childId,
|
|
after,
|
|
},
|
|
onError(error) {
|
|
toast.error(error.message);
|
|
},
|
|
});
|
|
};
|
|
|
|
return (
|
|
<Collapsible
|
|
key={space.id}
|
|
defaultOpen={true}
|
|
className="group/sidebar-space"
|
|
>
|
|
<div
|
|
className={cn(
|
|
'text-sm flex h-7 items-center gap-2 overflow-hidden rounded-md px-2 text-sidebar-foreground hover:bg-sidebar-accent hover:text-sidebar-accent-foreground cursor-pointer',
|
|
dropMonitor.isOver &&
|
|
dropMonitor.canDrop &&
|
|
'border-b-2 border-blue-300'
|
|
)}
|
|
ref={dropDivRef as RefAttributes<HTMLDivElement>['ref']}
|
|
>
|
|
<CollapsibleTrigger asChild>
|
|
<button className="group/space-button flex items-center gap-2 overflow-hidden rounded-md p-2 text-left text-sm flex-1 cursor-pointer">
|
|
<Avatar
|
|
id={space.id}
|
|
avatar={space.attributes.avatar}
|
|
name={space.attributes.name}
|
|
className="size-4 group-hover/space-button:hidden"
|
|
/>
|
|
<ChevronRight className="hidden size-4 transition-transform duration-200 group-hover/space-button:block group-data-[state=open]/sidebar-space:rotate-90" />
|
|
<span>{space.attributes.name}</span>
|
|
</button>
|
|
</CollapsibleTrigger>
|
|
<SpaceSidebarDropdown space={space} />
|
|
</div>
|
|
<CollapsibleContent>
|
|
<ul className="mx-3.5 flex min-w-0 translate-x-px flex-col gap-1 border-l border-sidebar-border px-2.5 py-0.5 mr-0 pr-0">
|
|
{children.map((child) => (
|
|
<li key={child.id}>
|
|
<Link
|
|
from="/workspace/$userId"
|
|
to="$nodeId"
|
|
params={{
|
|
nodeId: child.id,
|
|
}}
|
|
className="cursor-pointer select-none"
|
|
>
|
|
{({ isActive }) => (
|
|
<WorkspaceSidebarItem
|
|
node={child}
|
|
isActive={isActive}
|
|
canDrag={canEdit}
|
|
onDragEnd={(after) => {
|
|
handleDragEnd(child.id, after);
|
|
}}
|
|
/>
|
|
)}
|
|
</Link>
|
|
</li>
|
|
))}
|
|
</ul>
|
|
</CollapsibleContent>
|
|
</Collapsible>
|
|
);
|
|
};
|