Refactor container and breadcrumb

This commit is contained in:
Hakan Shehu
2025-09-21 01:36:19 +02:00
parent 8749446759
commit c71bcf6a7d
38 changed files with 551 additions and 490 deletions

View File

@@ -1,8 +1,10 @@
import { useNavigate } from '@tanstack/react-router';
import { LogOut } from 'lucide-react';
import { toast } from 'sonner';
import { Breadcrumb } from '@colanode/ui/components/layouts/breadcrumbs/breadcrumb';
import { BreadcrumbItem } from '@colanode/ui/components/layouts/breadcrumbs/breadcrumb-item';
import { Button } from '@colanode/ui/components/ui/button';
import { Container, ContainerBody } from '@colanode/ui/components/ui/container';
import { Separator } from '@colanode/ui/components/ui/separator';
import { Spinner } from '@colanode/ui/components/ui/spinner';
import { useAccount } from '@colanode/ui/contexts/account';
@@ -14,8 +16,14 @@ export const AccountLogoutScreen = () => {
const { mutate, isPending } = useMutation();
return (
<Container>
<ContainerBody className="max-w-4xl space-y-8">
<>
<Breadcrumb>
<BreadcrumbItem
icon={(className) => <LogOut className={className} />}
name="Logout"
/>
</Breadcrumb>
<div className="max-w-4xl space-y-8">
<div className="space-y-6">
<div>
<h2 className="text-2xl font-semibold tracking-tight">Logout</h2>
@@ -59,7 +67,7 @@ export const AccountLogoutScreen = () => {
</div>
</div>
</div>
</ContainerBody>
</Container>
</div>
</>
);
};

View File

@@ -1,12 +1,21 @@
import { Settings } from 'lucide-react';
import { AccountDelete } from '@colanode/ui/components/accounts/account-delete';
import { AccountUpdate } from '@colanode/ui/components/accounts/account-update';
import { Container, ContainerBody } from '@colanode/ui/components/ui/container';
import { Breadcrumb } from '@colanode/ui/components/layouts/breadcrumbs/breadcrumb';
import { BreadcrumbItem } from '@colanode/ui/components/layouts/breadcrumbs/breadcrumb-item';
import { Separator } from '@colanode/ui/components/ui/separator';
export const AccountSettingsScreen = () => {
return (
<Container>
<ContainerBody className="max-w-4xl space-y-8">
<>
<Breadcrumb>
<BreadcrumbItem
icon={(className) => <Settings className={className} />}
name="Settings"
/>
</Breadcrumb>
<div className="max-w-4xl space-y-8">
<div className="space-y-6">
<div>
<h2 className="text-2xl font-semibold tracking-tight">General</h2>
@@ -24,7 +33,7 @@ export const AccountSettingsScreen = () => {
</div>
<AccountDelete />
</div>
</ContainerBody>
</Container>
</div>
</>
);
};

View File

@@ -1,8 +1,9 @@
import { Check, Laptop, Moon, Sun } from 'lucide-react';
import { Check, Laptop, Moon, Palette, Sun } from 'lucide-react';
import { ThemeColor, ThemeMode } from '@colanode/client/types';
import { Breadcrumb } from '@colanode/ui/components/layouts/breadcrumbs/breadcrumb';
import { BreadcrumbItem } from '@colanode/ui/components/layouts/breadcrumbs/breadcrumb-item';
import { Button } from '@colanode/ui/components/ui/button';
import { Container, ContainerBody } from '@colanode/ui/components/ui/container';
import { Separator } from '@colanode/ui/components/ui/separator';
import { useMutation } from '@colanode/ui/hooks/use-mutation';
import { cn } from '@colanode/ui/lib/utils';
@@ -59,8 +60,14 @@ export const AppAppearanceSettingsScreen = () => {
const themeColor = useAppStore((state) => state.metadata.theme.color);
return (
<Container>
<ContainerBody className="max-w-4xl space-y-8">
<>
<Breadcrumb>
<BreadcrumbItem
icon={(className) => <Palette className={className} />}
name="Appearance"
/>
</Breadcrumb>
<div className="max-w-4xl space-y-8">
<div>
<h2 className="text-2xl font-semibold tracking-tight">Appearance</h2>
<Separator className="mt-3" />
@@ -171,7 +178,7 @@ export const AppAppearanceSettingsScreen = () => {
);
})}
</div>
</ContainerBody>
</Container>
</div>
</>
);
};

View File

@@ -1,5 +1,6 @@
import { LocalChannelNode } from '@colanode/client/types';
import { Avatar } from '@colanode/ui/components/avatars/avatar';
import { BreadcrumbItem } from '@colanode/ui/components/layouts/breadcrumbs/breadcrumb-item';
interface ChannelBreadcrumbItemProps {
channel: LocalChannelNode;
@@ -9,14 +10,16 @@ export const ChannelBreadcrumbItem = ({
channel,
}: ChannelBreadcrumbItemProps) => {
return (
<div className="flex items-center space-x-2">
<Avatar
id={channel.id}
name={channel.attributes.name}
avatar={channel.attributes.avatar}
className="size-4"
/>
<span>{channel.attributes.name}</span>
</div>
<BreadcrumbItem
icon={(className) => (
<Avatar
id={channel.id}
name={channel.attributes.name}
avatar={channel.attributes.avatar}
className={className}
/>
)}
name={channel.attributes.name}
/>
);
};

View File

@@ -1,14 +1,10 @@
import { LocalChannelNode } from '@colanode/client/types';
import { ChannelNotFound } from '@colanode/ui/components/channels/channel-not-found';
import { ChannelSettings } from '@colanode/ui/components/channels/channel-settings';
import { ContainerBreadcrumb } from '@colanode/ui/components/layouts/containers/container-breadrumb';
import { Breadcrumb } from '@colanode/ui/components/layouts/breadcrumbs/breadcrumb';
import { ContainerSettings } from '@colanode/ui/components/layouts/containers/container-settings';
import { Conversation } from '@colanode/ui/components/messages/conversation';
import {
Container,
ContainerBody,
ContainerHeader,
ContainerSettings,
} from '@colanode/ui/components/ui/container';
import { NodeBreadcrumb } from '@colanode/ui/components/nodes/node-breadcrumb';
import { useNodeContainer } from '@colanode/ui/hooks/use-node-container';
import { useNodeRadar } from '@colanode/ui/hooks/use-node-radar';
@@ -32,20 +28,18 @@ export const ChannelContainer = ({ channelId }: ChannelContainerProps) => {
const { node: channel, role } = data;
return (
<Container>
<ContainerHeader>
<ContainerBreadcrumb breadcrumb={data.breadcrumb} />
<ContainerSettings>
<ChannelSettings channel={channel} role={role} />
</ContainerSettings>
</ContainerHeader>
<ContainerBody>
<Conversation
conversationId={channel.id}
rootId={channel.rootId}
role={role}
/>
</ContainerBody>
</Container>
<>
<Breadcrumb>
<NodeBreadcrumb breadcrumb={data.breadcrumb} />
</Breadcrumb>
<ContainerSettings>
<ChannelSettings channel={channel} role={role} />
</ContainerSettings>
<Conversation
conversationId={channel.id}
rootId={channel.rootId}
role={role}
/>
</>
);
};

View File

@@ -1,5 +1,6 @@
import { LocalChatNode } from '@colanode/client/types';
import { Avatar } from '@colanode/ui/components/avatars/avatar';
import { BreadcrumbItem } from '@colanode/ui/components/layouts/breadcrumbs/breadcrumb-item';
import { useWorkspace } from '@colanode/ui/contexts/workspace';
import { useLiveQuery } from '@colanode/ui/hooks/use-live-query';
@@ -29,14 +30,16 @@ export const ChatBreadcrumbItem = ({ chat }: ChatBreadcrumbItemProps) => {
}
return (
<div className="flex items-center space-x-2">
<Avatar
id={userGetQuery.data.id}
name={userGetQuery.data.name}
avatar={userGetQuery.data.avatar}
className="size-4"
/>
<span>{userGetQuery.data.name}</span>
</div>
<BreadcrumbItem
icon={(className) => (
<Avatar
id={userGetQuery.data!.id}
name={userGetQuery.data!.name}
avatar={userGetQuery.data!.avatar}
className={className}
/>
)}
name={userGetQuery.data!.name}
/>
);
};

View File

@@ -1,14 +1,10 @@
import { LocalChatNode } from '@colanode/client/types';
import { ChatNotFound } from '@colanode/ui/components/chats/chat-not-found';
import { NodeCollaboratorsPopover } from '@colanode/ui/components/collaborators/node-collaborators-popover';
import { ContainerBreadcrumb } from '@colanode/ui/components/layouts/containers/container-breadrumb';
import { Breadcrumb } from '@colanode/ui/components/layouts/breadcrumbs/breadcrumb';
import { ContainerSettings } from '@colanode/ui/components/layouts/containers/container-settings';
import { Conversation } from '@colanode/ui/components/messages/conversation';
import {
Container,
ContainerBody,
ContainerHeader,
ContainerSettings,
} from '@colanode/ui/components/ui/container';
import { NodeBreadcrumb } from '@colanode/ui/components/nodes/node-breadcrumb';
import { useNodeContainer } from '@colanode/ui/hooks/use-node-container';
import { useNodeRadar } from '@colanode/ui/hooks/use-node-radar';
@@ -32,20 +28,14 @@ export const ChatContainer = ({ chatId }: ChatContainerProps) => {
const { node, role } = data;
return (
<Container>
<ContainerHeader>
<ContainerBreadcrumb breadcrumb={data.breadcrumb} />
<ContainerSettings>
<NodeCollaboratorsPopover node={node} nodes={[node]} role={role} />
</ContainerSettings>
</ContainerHeader>
<ContainerBody>
<Conversation
conversationId={node.id}
rootId={node.rootId}
role={role}
/>
</ContainerBody>
</Container>
<>
<Breadcrumb>
<NodeBreadcrumb breadcrumb={data.breadcrumb} />
</Breadcrumb>
<ContainerSettings>
<NodeCollaboratorsPopover node={node} nodes={[node]} role={role} />
</ContainerSettings>
<Conversation conversationId={node.id} rootId={node.rootId} role={role} />
</>
);
};

View File

@@ -1,5 +1,6 @@
import { LocalDatabaseNode } from '@colanode/client/types';
import { Avatar } from '@colanode/ui/components/avatars/avatar';
import { BreadcrumbItem } from '@colanode/ui/components/layouts/breadcrumbs/breadcrumb-item';
interface DatabaseBreadcrumbItemProps {
database: LocalDatabaseNode;
@@ -9,14 +10,16 @@ export const DatabaseBreadcrumbItem = ({
database,
}: DatabaseBreadcrumbItemProps) => {
return (
<div className="flex items-center space-x-2">
<Avatar
id={database.id}
name={database.attributes.name}
avatar={database.attributes.avatar}
className="size-4"
/>
<span>{database.attributes.name}</span>
</div>
<BreadcrumbItem
icon={(className) => (
<Avatar
id={database.id}
name={database.attributes.name}
avatar={database.attributes.avatar}
className={className}
/>
)}
name={database.attributes.name}
/>
);
};

View File

@@ -3,13 +3,9 @@ import { Database } from '@colanode/ui/components/databases/database';
import { DatabaseNotFound } from '@colanode/ui/components/databases/database-not-found';
import { DatabaseSettings } from '@colanode/ui/components/databases/database-settings';
import { DatabaseViews } from '@colanode/ui/components/databases/database-views';
import { ContainerBreadcrumb } from '@colanode/ui/components/layouts/containers/container-breadrumb';
import {
Container,
ContainerBody,
ContainerHeader,
ContainerSettings,
} from '@colanode/ui/components/ui/container';
import { Breadcrumb } from '@colanode/ui/components/layouts/breadcrumbs/breadcrumb';
import { ContainerSettings } from '@colanode/ui/components/layouts/containers/container-settings';
import { NodeBreadcrumb } from '@colanode/ui/components/nodes/node-breadcrumb';
import { useNodeContainer } from '@colanode/ui/hooks/use-node-container';
import { useNodeRadar } from '@colanode/ui/hooks/use-node-radar';
@@ -33,18 +29,16 @@ export const DatabaseContainer = ({ databaseId }: DatabaseContainerProps) => {
const { node: database, role } = data;
return (
<Container>
<ContainerHeader>
<ContainerBreadcrumb breadcrumb={data.breadcrumb} />
<ContainerSettings>
<DatabaseSettings database={database} role={role} />
</ContainerSettings>
</ContainerHeader>
<ContainerBody>
<Database database={database} role={role}>
<DatabaseViews />
</Database>
</ContainerBody>
</Container>
<>
<Breadcrumb>
<NodeBreadcrumb breadcrumb={data.breadcrumb} />
</Breadcrumb>
<ContainerSettings>
<DatabaseSettings database={database} role={role} />
</ContainerSettings>
<Database database={database} role={role}>
<DatabaseViews />
</Database>
</>
);
};

View File

@@ -1,5 +1,6 @@
import { LocalFileNode } from '@colanode/client/types';
import { FileThumbnail } from '@colanode/ui/components/files/file-thumbnail';
import { BreadcrumbItem } from '@colanode/ui/components/layouts/breadcrumbs/breadcrumb-item';
interface FileBreadcrumbItemProps {
file: LocalFileNode;
@@ -7,12 +8,9 @@ interface FileBreadcrumbItemProps {
export const FileBreadcrumbItem = ({ file }: FileBreadcrumbItemProps) => {
return (
<div className="flex items-center space-x-2">
<FileThumbnail
file={file}
className="size-4 overflow-hidden rounded object-contain"
/>
<span>{file.attributes.name}</span>
</div>
<BreadcrumbItem
icon={(className) => <FileThumbnail file={file} className={className} />}
name={file.attributes.name}
/>
);
};

View File

@@ -2,13 +2,9 @@ import { LocalFileNode } from '@colanode/client/types';
import { FileBody } from '@colanode/ui/components/files/file-body';
import { FileNotFound } from '@colanode/ui/components/files/file-not-found';
import { FileSettings } from '@colanode/ui/components/files/file-settings';
import { ContainerBreadcrumb } from '@colanode/ui/components/layouts/containers/container-breadrumb';
import {
Container,
ContainerBody,
ContainerHeader,
ContainerSettings,
} from '@colanode/ui/components/ui/container';
import { Breadcrumb } from '@colanode/ui/components/layouts/breadcrumbs/breadcrumb';
import { ContainerSettings } from '@colanode/ui/components/layouts/containers/container-settings';
import { NodeBreadcrumb } from '@colanode/ui/components/nodes/node-breadcrumb';
import { useNodeContainer } from '@colanode/ui/hooks/use-node-container';
import { useNodeRadar } from '@colanode/ui/hooks/use-node-radar';
@@ -29,16 +25,14 @@ export const FileContainer = ({ fileId }: FileContainerProps) => {
}
return (
<Container>
<ContainerHeader>
<ContainerBreadcrumb breadcrumb={data.breadcrumb} />
<ContainerSettings>
<FileSettings file={data.node} role={data.role} />
</ContainerSettings>
</ContainerHeader>
<ContainerBody>
<FileBody file={data.node} />
</ContainerBody>
</Container>
<>
<Breadcrumb>
<NodeBreadcrumb breadcrumb={data.breadcrumb} />
</Breadcrumb>
<ContainerSettings>
<FileSettings file={data.node} role={data.role} />
</ContainerSettings>
<FileBody file={data.node} />
</>
);
};

View File

@@ -1,5 +1,6 @@
import { LocalFolderNode } from '@colanode/client/types';
import { Avatar } from '@colanode/ui/components/avatars/avatar';
import { BreadcrumbItem } from '@colanode/ui/components/layouts/breadcrumbs/breadcrumb-item';
interface FolderBreadcrumbItemProps {
folder: LocalFolderNode;
@@ -7,14 +8,16 @@ interface FolderBreadcrumbItemProps {
export const FolderBreadcrumbItem = ({ folder }: FolderBreadcrumbItemProps) => {
return (
<div className="flex items-center space-x-2">
<Avatar
id={folder.id}
name={folder.attributes.name}
avatar={folder.attributes.avatar}
className="size-4"
/>
<span>{folder.attributes.name}</span>
</div>
<BreadcrumbItem
icon={(className) => (
<Avatar
id={folder.id}
name={folder.attributes.name}
avatar={folder.attributes.avatar}
className={className}
/>
)}
name={folder.attributes.name}
/>
);
};

View File

@@ -2,13 +2,9 @@ import { LocalFolderNode } from '@colanode/client/types';
import { FolderBody } from '@colanode/ui/components/folders/folder-body';
import { FolderNotFound } from '@colanode/ui/components/folders/folder-not-found';
import { FolderSettings } from '@colanode/ui/components/folders/folder-settings';
import { ContainerBreadcrumb } from '@colanode/ui/components/layouts/containers/container-breadrumb';
import {
Container,
ContainerBody,
ContainerHeader,
ContainerSettings,
} from '@colanode/ui/components/ui/container';
import { Breadcrumb } from '@colanode/ui/components/layouts/breadcrumbs/breadcrumb';
import { ContainerSettings } from '@colanode/ui/components/layouts/containers/container-settings';
import { NodeBreadcrumb } from '@colanode/ui/components/nodes/node-breadcrumb';
import { useNodeContainer } from '@colanode/ui/hooks/use-node-container';
import { useNodeRadar } from '@colanode/ui/hooks/use-node-radar';
@@ -32,16 +28,14 @@ export const FolderContainer = ({ folderId }: FolderContainerProps) => {
const { node: folder, role } = data;
return (
<Container>
<ContainerHeader>
<ContainerBreadcrumb breadcrumb={data.breadcrumb} />
<ContainerSettings>
<FolderSettings folder={folder} role={role} />
</ContainerSettings>
</ContainerHeader>
<ContainerBody>
<FolderBody folder={folder} role={role} />
</ContainerBody>
</Container>
<>
<Breadcrumb>
<NodeBreadcrumb breadcrumb={data.breadcrumb} />
</Breadcrumb>
<ContainerSettings>
<FolderSettings folder={folder} role={role} />
</ContainerSettings>
<FolderBody folder={folder} role={role} />
</>
);
};

View File

@@ -0,0 +1,25 @@
import { cn } from '@colanode/ui/lib/utils';
interface BreadcrumbItemProps {
icon: (className: string) => React.ReactNode;
name: string;
className?: string;
}
export const BreadcrumbItem = ({
icon,
name,
className,
}: BreadcrumbItemProps) => {
return (
<div
className={cn(
'text-muted-foreground flex items-center space-x-2 hover:text-foreground cursor-pointer text-sm',
className
)}
>
{icon('size-4')}
<span>{name}</span>
</div>
);
};

View File

@@ -0,0 +1,21 @@
import { useEffect } from 'react';
import { useContainer } from '@colanode/ui/contexts/container';
interface BreadcrumbProps {
children: React.ReactNode;
}
export const Breadcrumb = ({ children }: BreadcrumbProps) => {
const container = useContainer();
useEffect(() => {
container.setBreadcrumb(children);
return () => {
container.resetBreadcrumb();
};
}, [children]);
return null;
};

View File

@@ -0,0 +1,21 @@
import { useEffect } from 'react';
import { useContainer } from '@colanode/ui/contexts/container';
interface ContainerSettingsProps {
children: React.ReactNode;
}
export const ContainerSettings = ({ children }: ContainerSettingsProps) => {
const container = useContainer();
useEffect(() => {
container.setSettings(children);
return () => {
container.resetSettings();
};
}, [children]);
return null;
};

View File

@@ -0,0 +1,34 @@
import { Outlet } from '@tanstack/react-router';
import { useState } from 'react';
import { SidebarMobile } from '@colanode/ui/components/layouts/sidebars/sidebar-mobile';
import { ContainerContext } from '@colanode/ui/contexts/container';
import { useIsMobile } from '@colanode/ui/hooks/use-is-mobile';
export const Container = () => {
const isMobile = useIsMobile();
const [settings, setSettings] = useState<React.ReactNode>(null);
const [breadcrumb, setBreadcrumb] = useState<React.ReactNode>(null);
return (
<ContainerContext.Provider
value={{
setSettings,
setBreadcrumb,
resetSettings: () => setSettings(null),
resetBreadcrumb: () => setBreadcrumb(null),
}}
>
<div className="flex flex-col w-full h-full min-w-full min-h-full">
<div className="flex flex-row w-full items-center gap-2 p-3">
{isMobile && <SidebarMobile />}
{breadcrumb && <div className="flex-1">{breadcrumb}</div>}
{settings}
</div>
<div className="lg:px-10 px-4 lg:py-4 py-2 flex-grow max-h-full h-full overflow-hidden">
<Outlet />
</div>
</div>
</ContainerContext.Provider>
);
};

View File

@@ -1,5 +1,4 @@
import { Outlet } from '@tanstack/react-router';
import { Container } from '@colanode/ui/components/layouts/containers/container';
import { SidebarDesktop } from '@colanode/ui/components/layouts/sidebars/sidebar-desktop';
import { useIsMobile } from '@colanode/ui/hooks/use-is-mobile';
@@ -10,7 +9,7 @@ export const Layout = () => {
<div className="w-screen min-w-screen h-screen min-h-screen flex flex-row bg-background">
{!isMobile && <SidebarDesktop />}
<div className="h-full max-h-screen w-full flex-grow overflow-hidden">
<Outlet />
<Container />
</div>
</div>
);

View File

@@ -1,18 +1,19 @@
import { MessageCircle } from 'lucide-react';
import { LocalMessageNode } from '@colanode/client/types';
import { BreadcrumbItem } from '@colanode/ui/components/layouts/breadcrumbs/breadcrumb-item';
interface MessageBreadcrumbItemProps {
message: LocalMessageNode;
}
export const MessageBreadcrumbItem = ({
message,
message: _,
}: MessageBreadcrumbItemProps) => {
return (
<div className="flex items-center space-x-2" id={message.id}>
<MessageCircle className="size-4" />
<span>Message</span>
</div>
<BreadcrumbItem
icon={(className) => <MessageCircle className={className} />}
name="Message"
/>
);
};

View File

@@ -1,12 +1,8 @@
import { LocalMessageNode } from '@colanode/client/types';
import { ContainerBreadcrumb } from '@colanode/ui/components/layouts/containers/container-breadrumb';
import { Breadcrumb } from '@colanode/ui/components/layouts/breadcrumbs/breadcrumb';
import { Message } from '@colanode/ui/components/messages/message';
import { MessageNotFound } from '@colanode/ui/components/messages/message-not-found';
import {
Container,
ContainerBody,
ContainerHeader,
} from '@colanode/ui/components/ui/container';
import { NodeBreadcrumb } from '@colanode/ui/components/nodes/node-breadcrumb';
import { ConversationContext } from '@colanode/ui/contexts/conversation';
import { useNodeContainer } from '@colanode/ui/hooks/use-node-container';
import { useNodeRadar } from '@colanode/ui/hooks/use-node-radar';
@@ -29,25 +25,23 @@ export const MessageContainer = ({ messageId }: MessageContainerProps) => {
}
return (
<Container>
<ContainerHeader>
<ContainerBreadcrumb breadcrumb={data.breadcrumb} />
</ContainerHeader>
<ContainerBody>
<ConversationContext.Provider
value={{
id: data.node.id,
role: data.role,
rootId: data.node.rootId,
canCreateMessage: true,
onReply: () => {},
onLastMessageIdChange: () => {},
canDeleteMessage: () => false,
}}
>
<Message message={data.node} />
</ConversationContext.Provider>
</ContainerBody>
</Container>
<>
<Breadcrumb>
<NodeBreadcrumb breadcrumb={data.breadcrumb} />
</Breadcrumb>
<ConversationContext.Provider
value={{
id: data.node.id,
role: data.role,
rootId: data.node.rootId,
canCreateMessage: true,
onReply: () => {},
onLastMessageIdChange: () => {},
canDeleteMessage: () => false,
}}
>
<Message message={data.node} />
</ConversationContext.Provider>
</>
);
};

View File

@@ -9,13 +9,11 @@ import { PageBreadcrumbItem } from '@colanode/ui/components/pages/page-breadcrum
import { RecordBreadcrumbItem } from '@colanode/ui/components/records/record-breadcrumb-item';
import { SpaceBreadcrumbItem } from '@colanode/ui/components/spaces/space-breadcrumb-item';
interface ContainerBreadcrumbItemProps {
interface NodeBreadcrumbItemProps {
node: LocalNode;
}
export const ContainerBreadcrumbItem = ({
node,
}: ContainerBreadcrumbItemProps) => {
export const NodeBreadcrumbItem = ({ node }: NodeBreadcrumbItemProps) => {
switch (node.type) {
case 'space':
return <SpaceBreadcrumbItem space={node} />;

View File

@@ -2,7 +2,7 @@ import { Link } from '@tanstack/react-router';
import { Fragment } from 'react';
import { LocalNode } from '@colanode/client/types';
import { ContainerBreadcrumbItem } from '@colanode/ui/components/layouts/containers/container-breadcrumb-item';
import { NodeBreadcrumbItem } from '@colanode/ui/components/nodes/node-breadcrumb-item';
import {
Breadcrumb,
BreadcrumbEllipsis,
@@ -17,13 +17,11 @@ import {
DropdownMenuTrigger,
} from '@colanode/ui/components/ui/dropdown-menu';
interface ContainerBreadcrumbProps {
interface NodeBreadcrumbProps {
breadcrumb: LocalNode[];
}
export const ContainerBreadcrumb = ({
breadcrumb,
}: ContainerBreadcrumbProps) => {
export const NodeBreadcrumb = ({ breadcrumb }: NodeBreadcrumbProps) => {
// Show ellipsis if we have more than 3 nodes (first + last two)
const showEllipsis = breadcrumb.length > 3;
@@ -54,7 +52,7 @@ export const ContainerBreadcrumb = ({
to="$nodeId"
params={{ nodeId: item.id }}
>
<ContainerBreadcrumbItem node={item} />
<NodeBreadcrumbItem node={item} />
</Link>
</BreadcrumbItem>
{showEllipsis && isFirst && (
@@ -75,9 +73,7 @@ export const ContainerBreadcrumb = ({
params={{ nodeId: ellipsisItem.id }}
>
<BreadcrumbItem className="cursor-pointer hover:text-foreground">
<ContainerBreadcrumbItem
node={ellipsisItem}
/>
<NodeBreadcrumbItem node={ellipsisItem} />
</BreadcrumbItem>
</Link>
</DropdownMenuItem>

View File

@@ -1,25 +1,25 @@
import { BadgeAlert } from 'lucide-react';
import {
Container,
ContainerBody,
ContainerHeader,
} from '@colanode/ui/components/ui/container';
import { Breadcrumb } from '@colanode/ui/components/layouts/breadcrumbs/breadcrumb';
import { BreadcrumbItem } from '@colanode/ui/components/layouts/breadcrumbs/breadcrumb-item';
export const NodeErrorScreen = () => {
return (
<Container>
<ContainerHeader />
<ContainerBody>
<div className="flex flex-col items-center justify-center h-full p-6 text-center">
<BadgeAlert className="size-12 mb-4" />
<h1 className="text-2xl font-semibold tracking-tight">Node error</h1>
<p className="mt-2 text-sm font-medium text-muted-foreground">
The node you are looking for does not exist. It may have been
deleted or your access has been removed.
</p>
</div>
</ContainerBody>
</Container>
<>
<Breadcrumb>
<BreadcrumbItem
icon={(className) => <BadgeAlert className={className} />}
name="Node error"
/>
</Breadcrumb>
<div className="flex flex-col items-center justify-center h-full p-6 text-center">
<BadgeAlert className="size-12 mb-4" />
<h1 className="text-2xl font-semibold tracking-tight">Node error</h1>
<p className="mt-2 text-sm font-medium text-muted-foreground">
The node you are looking for does not exist. It may have been deleted
or your access has been removed.
</p>
</div>
</>
);
};

View File

@@ -1,5 +1,6 @@
import { LocalPageNode } from '@colanode/client/types';
import { Avatar } from '@colanode/ui/components/avatars/avatar';
import { BreadcrumbItem } from '@colanode/ui/components/layouts/breadcrumbs/breadcrumb-item';
interface PageBreadcrumbItemProps {
page: LocalPageNode;
@@ -7,14 +8,16 @@ interface PageBreadcrumbItemProps {
export const PageBreadcrumbItem = ({ page }: PageBreadcrumbItemProps) => {
return (
<div className="flex items-center space-x-2">
<Avatar
id={page.id}
name={page.attributes.name}
avatar={page.attributes.avatar}
className="size-4"
/>
<span>{page.attributes.name}</span>
</div>
<BreadcrumbItem
icon={(className) => (
<Avatar
id={page.id}
name={page.attributes.name}
avatar={page.attributes.avatar}
className={className}
/>
)}
name={page.attributes.name}
/>
);
};

View File

@@ -1,14 +1,10 @@
import { LocalPageNode } from '@colanode/client/types';
import { ContainerBreadcrumb } from '@colanode/ui/components/layouts/containers/container-breadrumb';
import { Breadcrumb } from '@colanode/ui/components/layouts/breadcrumbs/breadcrumb';
import { ContainerSettings } from '@colanode/ui/components/layouts/containers/container-settings';
import { NodeBreadcrumb } from '@colanode/ui/components/nodes/node-breadcrumb';
import { PageBody } from '@colanode/ui/components/pages/page-body';
import { PageNotFound } from '@colanode/ui/components/pages/page-not-found';
import { PageSettings } from '@colanode/ui/components/pages/page-settings';
import {
Container,
ContainerBody,
ContainerHeader,
ContainerSettings,
} from '@colanode/ui/components/ui/container';
import { useNodeContainer } from '@colanode/ui/hooks/use-node-container';
import { useNodeRadar } from '@colanode/ui/hooks/use-node-radar';
@@ -31,16 +27,14 @@ export const PageContainer = ({ pageId }: PageContainerProps) => {
const { node: page, role } = data;
return (
<Container>
<ContainerHeader>
<ContainerBreadcrumb breadcrumb={data.breadcrumb} />
<ContainerSettings>
<PageSettings page={page} role={role} />
</ContainerSettings>
</ContainerHeader>
<ContainerBody>
<PageBody page={page} role={role} />
</ContainerBody>
</Container>
<>
<Breadcrumb>
<NodeBreadcrumb breadcrumb={data.breadcrumb} />
</Breadcrumb>
<ContainerSettings>
<PageSettings page={page} role={role} />
</ContainerSettings>
<PageBody page={page} role={role} />
</>
);
};

View File

@@ -1,5 +1,6 @@
import { LocalRecordNode } from '@colanode/client/types';
import { Avatar } from '@colanode/ui/components/avatars/avatar';
import { BreadcrumbItem } from '@colanode/ui/components/layouts/breadcrumbs/breadcrumb-item';
interface RecordBreadcrumbItemProps {
record: LocalRecordNode;
@@ -7,14 +8,16 @@ interface RecordBreadcrumbItemProps {
export const RecordBreadcrumbItem = ({ record }: RecordBreadcrumbItemProps) => {
return (
<div className="flex items-center space-x-2">
<Avatar
id={record.id}
name={record.attributes.name}
avatar={record.attributes.avatar}
className="size-4"
/>
<span>{record.attributes.name}</span>
</div>
<BreadcrumbItem
icon={(className) => (
<Avatar
id={record.id}
name={record.attributes.name}
avatar={record.attributes.avatar}
className={className}
/>
)}
name={record.attributes.name}
/>
);
};

View File

@@ -1,14 +1,10 @@
import { LocalRecordNode } from '@colanode/client/types';
import { ContainerBreadcrumb } from '@colanode/ui/components/layouts/containers/container-breadrumb';
import { Breadcrumb } from '@colanode/ui/components/layouts/breadcrumbs/breadcrumb';
import { ContainerSettings } from '@colanode/ui/components/layouts/containers/container-settings';
import { NodeBreadcrumb } from '@colanode/ui/components/nodes/node-breadcrumb';
import { RecordBody } from '@colanode/ui/components/records/record-body';
import { RecordNotFound } from '@colanode/ui/components/records/record-not-found';
import { RecordSettings } from '@colanode/ui/components/records/record-settings';
import {
Container,
ContainerBody,
ContainerHeader,
ContainerSettings,
} from '@colanode/ui/components/ui/container';
import { useNodeContainer } from '@colanode/ui/hooks/use-node-container';
import { useNodeRadar } from '@colanode/ui/hooks/use-node-radar';
@@ -32,16 +28,14 @@ export const RecordContainer = ({ recordId }: RecordContainerProps) => {
const { node: record, role } = data;
return (
<Container>
<ContainerHeader>
<ContainerBreadcrumb breadcrumb={data.breadcrumb} />
<ContainerSettings>
<RecordSettings record={record} role={role} />
</ContainerSettings>
</ContainerHeader>
<ContainerBody>
<RecordBody record={record} role={role} />
</ContainerBody>
</Container>
<>
<Breadcrumb>
<NodeBreadcrumb breadcrumb={data.breadcrumb} />
</Breadcrumb>
<ContainerSettings>
<RecordSettings record={record} role={role} />
</ContainerSettings>
<RecordBody record={record} role={role} />
</>
);
};

View File

@@ -6,7 +6,6 @@ import { NodeRole, hasNodeRole } from '@colanode/core';
import { NodeCollaborators } from '@colanode/ui/components/collaborators/node-collaborators';
import { SpaceDelete } from '@colanode/ui/components/spaces/space-delete';
import { SpaceForm } from '@colanode/ui/components/spaces/space-form';
import { Container, ContainerBody } from '@colanode/ui/components/ui/container';
import {
ScrollArea,
ScrollBar,
@@ -30,83 +29,77 @@ export const SpaceBody = ({ space, role }: SpaceBodyProps) => {
const canDelete = hasNodeRole(role, 'admin');
return (
<Container>
<ContainerBody>
<ScrollArea className="relative overflow-hidden">
<ScrollViewport className="h-full max-h-[calc(100vh-100px)] w-full overflow-y-auto rounded-[inherit]">
<div className="max-w-4xl space-y-8 w-full pb-10">
<div className="space-y-6">
<div>
<h2 className="text-2xl font-semibold tracking-tight">
General
</h2>
<Separator className="mt-3" />
</div>
<SpaceForm
values={{
name: space.attributes.name,
description: space.attributes.description ?? '',
avatar: space.attributes.avatar ?? null,
}}
readOnly={!canEdit}
onSubmit={(values) => {
mutate({
input: {
type: 'space.update',
accountId: workspace.accountId,
workspaceId: workspace.id,
spaceId: space.id,
name: values.name,
description: values.description,
avatar: values.avatar,
},
onSuccess() {
toast.success('Space updated');
},
onError(error) {
toast.error(error.message);
},
});
}}
isSaving={isPending}
saveText="Update"
/>
</div>
<div className="space-y-6">
<div>
<h2 className="text-2xl font-semibold tracking-tight">
Collaborators
</h2>
<Separator className="mt-3" />
</div>
<NodeCollaborators node={space} nodes={[space]} role={role} />
</div>
{canDelete && (
<div className="space-y-6">
<div>
<h2 className="text-2xl font-semibold tracking-tight">
Danger Zone
</h2>
<Separator className="mt-3" />
</div>
<SpaceDelete
id={space.id}
onDeleted={() => {
navigate({
to: '/',
});
}}
/>
</div>
)}
<ScrollArea className="relative overflow-hidden">
<ScrollViewport className="h-full max-h-[calc(100vh-100px)] w-full overflow-y-auto rounded-[inherit]">
<div className="max-w-4xl space-y-8 w-full pb-10">
<div className="space-y-6">
<div>
<h2 className="text-2xl font-semibold tracking-tight">General</h2>
<Separator className="mt-3" />
</div>
</ScrollViewport>
<ScrollBar orientation="horizontal" />
<ScrollBar orientation="vertical" />
</ScrollArea>
</ContainerBody>
</Container>
<SpaceForm
values={{
name: space.attributes.name,
description: space.attributes.description ?? '',
avatar: space.attributes.avatar ?? null,
}}
readOnly={!canEdit}
onSubmit={(values) => {
mutate({
input: {
type: 'space.update',
accountId: workspace.accountId,
workspaceId: workspace.id,
spaceId: space.id,
name: values.name,
description: values.description,
avatar: values.avatar,
},
onSuccess() {
toast.success('Space updated');
},
onError(error) {
toast.error(error.message);
},
});
}}
isSaving={isPending}
saveText="Update"
/>
</div>
<div className="space-y-6">
<div>
<h2 className="text-2xl font-semibold tracking-tight">
Collaborators
</h2>
<Separator className="mt-3" />
</div>
<NodeCollaborators node={space} nodes={[space]} role={role} />
</div>
{canDelete && (
<div className="space-y-6">
<div>
<h2 className="text-2xl font-semibold tracking-tight">
Danger Zone
</h2>
<Separator className="mt-3" />
</div>
<SpaceDelete
id={space.id}
onDeleted={() => {
navigate({
to: '/',
});
}}
/>
</div>
)}
</div>
</ScrollViewport>
<ScrollBar orientation="horizontal" />
<ScrollBar orientation="vertical" />
</ScrollArea>
);
};

View File

@@ -1,5 +1,6 @@
import { LocalSpaceNode } from '@colanode/client/types';
import { Avatar } from '@colanode/ui/components/avatars/avatar';
import { BreadcrumbItem } from '@colanode/ui/components/layouts/breadcrumbs/breadcrumb-item';
interface SpaceBreadcrumbItemProps {
space: LocalSpaceNode;
@@ -7,14 +8,16 @@ interface SpaceBreadcrumbItemProps {
export const SpaceBreadcrumbItem = ({ space }: SpaceBreadcrumbItemProps) => {
return (
<div className="flex items-center space-x-2">
<Avatar
id={space.id}
name={space.attributes.name}
avatar={space.attributes.avatar}
className="size-4"
/>
<span>{space.attributes.name}</span>
</div>
<BreadcrumbItem
icon={(className) => (
<Avatar
id={space.id}
name={space.attributes.name}
avatar={space.attributes.avatar}
className={className}
/>
)}
name={space.attributes.name}
/>
);
};

View File

@@ -1,12 +1,8 @@
import { LocalSpaceNode } from '@colanode/client/types';
import { ContainerBreadcrumb } from '@colanode/ui/components/layouts/containers/container-breadrumb';
import { Breadcrumb } from '@colanode/ui/components/layouts/breadcrumbs/breadcrumb';
import { NodeBreadcrumb } from '@colanode/ui/components/nodes/node-breadcrumb';
import { SpaceBody } from '@colanode/ui/components/spaces/space-body';
import { SpaceNotFound } from '@colanode/ui/components/spaces/space-not-found';
import {
Container,
ContainerBody,
ContainerHeader,
} from '@colanode/ui/components/ui/container';
import { useNodeContainer } from '@colanode/ui/hooks/use-node-container';
import { useNodeRadar } from '@colanode/ui/hooks/use-node-radar';
@@ -30,13 +26,11 @@ export const SpaceContainer = ({ spaceId }: SpaceContainerProps) => {
const { node, role } = data;
return (
<Container>
<ContainerHeader>
<ContainerBreadcrumb breadcrumb={data.breadcrumb} />
</ContainerHeader>
<ContainerBody>
<SpaceBody space={node} role={role} />
</ContainerBody>
</Container>
<>
<Breadcrumb>
<NodeBreadcrumb breadcrumb={data.breadcrumb} />
</Breadcrumb>
<SpaceBody space={node} role={role} />
</>
);
};

View File

@@ -1,69 +0,0 @@
import { SidebarMobile } from '@colanode/ui/components/layouts/sidebars/sidebar-mobile';
import { useIsMobile } from '@colanode/ui/hooks/use-is-mobile';
import { cn } from '@colanode/ui/lib/utils';
export const Container = ({
children,
className,
...props
}: React.HTMLAttributes<HTMLDivElement>) => {
return (
<div
className={cn(
'flex flex-col w-full h-full min-w-full min-h-full',
className
)}
{...props}
>
{children}
</div>
);
};
export const ContainerHeader = ({
children,
className,
...props
}: React.HTMLAttributes<HTMLDivElement>) => {
const isMobile = useIsMobile();
return (
<div
className={cn('flex flex-row w-full items-center gap-2 p-3', className)}
{...props}
>
{isMobile && <SidebarMobile />}
{children}
</div>
);
};
export const ContainerBody = ({
children,
className,
...props
}: React.HTMLAttributes<HTMLDivElement>) => {
return (
<div
className={cn(
'lg:px-10 px-4 lg:py-4 py-2 flex-grow max-h-full h-full overflow-hidden',
className
)}
{...props}
>
{children}
</div>
);
};
export const ContainerSettings = ({
children,
className,
...props
}: React.HTMLAttributes<HTMLDivElement>) => {
return (
<div className={cn('flex items-center gap-2', className)} {...props}>
{children}
</div>
);
};

View File

@@ -1,8 +1,10 @@
import { Download } from 'lucide-react';
import { useState } from 'react';
import { InView } from 'react-intersection-observer';
import { DownloadListManualQueryInput } from '@colanode/client/queries';
import { Container, ContainerBody } from '@colanode/ui/components/ui/container';
import { Breadcrumb } from '@colanode/ui/components/layouts/breadcrumbs/breadcrumb';
import { BreadcrumbItem } from '@colanode/ui/components/layouts/breadcrumbs/breadcrumb-item';
import { Separator } from '@colanode/ui/components/ui/separator';
import { WorkspaceDownloadFile } from '@colanode/ui/components/workspaces/downloads/workspace-download-file';
import { useWorkspace } from '@colanode/ui/contexts/workspace';
@@ -32,8 +34,14 @@ export const WorkspaceDownloadsScreen = () => {
!isPending && downloads.length === lastPage * DOWNLOADS_PER_PAGE;
return (
<Container>
<ContainerBody className="overflow-y-auto">
<>
<Breadcrumb>
<BreadcrumbItem
icon={(className) => <Download className={className} />}
name="Downloads"
/>
</Breadcrumb>
<div className="overflow-y-auto">
<div className="max-w-4xl space-y-10">
<div>
<h2 className="text-2xl font-semibold tracking-tight">Downloads</h2>
@@ -53,7 +61,7 @@ export const WorkspaceDownloadsScreen = () => {
}}
/>
</div>
</ContainerBody>
</Container>
</div>
</>
);
};

View File

@@ -1,4 +1,7 @@
import { Container, ContainerBody } from '@colanode/ui/components/ui/container';
import { Cylinder } from 'lucide-react';
import { Breadcrumb } from '@colanode/ui/components/layouts/breadcrumbs/breadcrumb';
import { BreadcrumbItem } from '@colanode/ui/components/layouts/breadcrumbs/breadcrumb-item';
import { UserStorageStats } from '@colanode/ui/components/workspaces/storage/user-storage-stats';
import { WorkspaceStorageStats } from '@colanode/ui/components/workspaces/storage/workspace-storage-stats';
import { useWorkspace } from '@colanode/ui/contexts/workspace';
@@ -9,11 +12,17 @@ export const WorkspaceStorageScreen = () => {
workspace.role === 'owner' || workspace.role === 'admin';
return (
<Container>
<ContainerBody className="max-w-4xl space-y-10">
<>
<Breadcrumb>
<BreadcrumbItem
icon={(className) => <Cylinder className={className} />}
name="Storage"
/>
</Breadcrumb>
<div className="max-w-4xl space-y-10">
<UserStorageStats />
{canManageStorage && <WorkspaceStorageStats />}
</ContainerBody>
</Container>
</div>
</>
);
};

View File

@@ -1,8 +1,10 @@
import { Upload } from 'lucide-react';
import { useState } from 'react';
import { InView } from 'react-intersection-observer';
import { UploadListQueryInput } from '@colanode/client/queries';
import { Container, ContainerBody } from '@colanode/ui/components/ui/container';
import { Breadcrumb } from '@colanode/ui/components/layouts/breadcrumbs/breadcrumb';
import { BreadcrumbItem } from '@colanode/ui/components/layouts/breadcrumbs/breadcrumb-item';
import { Separator } from '@colanode/ui/components/ui/separator';
import { WorkspaceUploadFile } from '@colanode/ui/components/workspaces/uploads/workspace-upload-file';
import { useWorkspace } from '@colanode/ui/contexts/workspace';
@@ -31,8 +33,14 @@ export const WorkspaceUploadsScreen = () => {
const hasMore = !isPending && uploads.length === lastPage * UPLOADS_PER_PAGE;
return (
<Container>
<ContainerBody className="overflow-y-auto">
<>
<Breadcrumb>
<BreadcrumbItem
icon={(className) => <Upload className={className} />}
name="Uploads"
/>
</Breadcrumb>
<div className="overflow-y-auto">
<div className="max-w-4xl space-y-10">
<div>
<h2 className="text-2xl font-semibold tracking-tight">Uploads</h2>
@@ -52,7 +60,7 @@ export const WorkspaceUploadsScreen = () => {
}}
/>
</div>
</ContainerBody>
</Container>
</div>
</>
);
};

View File

@@ -1,23 +1,25 @@
import {
Container,
ContainerBody,
ContainerHeader,
} from '@colanode/ui/components/ui/container';
import { Home } from 'lucide-react';
import { Breadcrumb } from '@colanode/ui/components/layouts/breadcrumbs/breadcrumb';
import { BreadcrumbItem } from '@colanode/ui/components/layouts/breadcrumbs/breadcrumb-item';
export const WorkspaceHomeScreen = () => {
return (
<Container>
<ContainerHeader />
<ContainerBody>
<div className="h-full w-full flex flex-col gap-1">
<div className="h-10 app-drag-region"></div>
<div className="flex-grow flex items-center justify-center">
<p className="text-sm text-muted-foreground">
What did you get done this week?
</p>
</div>
<>
<Breadcrumb>
<BreadcrumbItem
icon={(className) => <Home className={className} />}
name="Home"
/>
</Breadcrumb>
<div className="h-full w-full flex flex-col gap-1">
<div className="h-10 app-drag-region"></div>
<div className="flex-grow flex items-center justify-center">
<p className="text-sm text-muted-foreground">
What did you get done this week?
</p>
</div>
</ContainerBody>
</Container>
</div>
</>
);
};

View File

@@ -1,6 +1,8 @@
import { Settings } from 'lucide-react';
import { toast } from 'sonner';
import { Container, ContainerBody } from '@colanode/ui/components/ui/container';
import { Breadcrumb } from '@colanode/ui/components/layouts/breadcrumbs/breadcrumb';
import { BreadcrumbItem } from '@colanode/ui/components/layouts/breadcrumbs/breadcrumb-item';
import { Separator } from '@colanode/ui/components/ui/separator';
import { WorkspaceDelete } from '@colanode/ui/components/workspaces/workspace-delete';
import { WorkspaceForm } from '@colanode/ui/components/workspaces/workspace-form';
@@ -23,8 +25,14 @@ export const WorkspaceSettingsScreen = () => {
}
return (
<Container>
<ContainerBody className="max-w-4xl space-y-8">
<>
<Breadcrumb>
<BreadcrumbItem
icon={(className) => <Settings className={className} />}
name="Settings"
/>
</Breadcrumb>
<div className="max-w-4xl space-y-8">
<div className="space-y-6">
<div>
<h2 className="text-2xl font-semibold tracking-tight">General</h2>
@@ -69,7 +77,7 @@ export const WorkspaceSettingsScreen = () => {
</div>
<WorkspaceDelete />
</div>
</ContainerBody>
</Container>
</div>
</>
);
};

View File

@@ -1,10 +1,12 @@
import { Users } from 'lucide-react';
import { useState } from 'react';
import { InView } from 'react-intersection-observer';
import { UserListQueryInput } from '@colanode/client/queries';
import { WorkspaceRole } from '@colanode/core';
import { Avatar } from '@colanode/ui/components/avatars/avatar';
import { Container, ContainerBody } from '@colanode/ui/components/ui/container';
import { Breadcrumb } from '@colanode/ui/components/layouts/breadcrumbs/breadcrumb';
import { BreadcrumbItem } from '@colanode/ui/components/layouts/breadcrumbs/breadcrumb-item';
import { Separator } from '@colanode/ui/components/ui/separator';
import { Spinner } from '@colanode/ui/components/ui/spinner';
import { WorkspaceUserInvite } from '@colanode/ui/components/workspaces/workspace-user-invite';
@@ -35,8 +37,14 @@ export const WorkspaceUsersScreen = () => {
const hasMore = !isPending && users.length === lastPage * USERS_PER_PAGE;
return (
<Container>
<ContainerBody className="max-w-4xl space-y-8">
<>
<Breadcrumb>
<BreadcrumbItem
icon={(className) => <Users className={className} />}
name="Users"
/>
</Breadcrumb>
<div className="max-w-4xl space-y-8">
{canEditUsers && (
<div className="space-y-6">
<div>
@@ -94,7 +102,7 @@ export const WorkspaceUsersScreen = () => {
/>
</div>
</div>
</ContainerBody>
</Container>
</div>
</>
);
};

View File

@@ -0,0 +1,14 @@
import { createContext, useContext } from 'react';
interface ContainerContext {
setSettings: (settings: React.ReactNode) => void;
resetSettings: () => void;
setBreadcrumb: (breadcrumb: React.ReactNode) => void;
resetBreadcrumb: () => void;
}
export const ContainerContext = createContext<ContainerContext>(
{} as ContainerContext
);
export const useContainer = () => useContext(ContainerContext);