mirror of
https://github.com/colanode/colanode.git
synced 2025-12-16 11:47:47 +01:00
tabs improvements
This commit is contained in:
21
packages/ui/src/components/channels/channel-tab.tsx
Normal file
21
packages/ui/src/components/channels/channel-tab.tsx
Normal file
@@ -0,0 +1,21 @@
|
||||
import { LocalChannelNode } from '@colanode/client/types';
|
||||
import { Tab } from '@colanode/ui/components/layouts/tabs/tab';
|
||||
|
||||
interface ChannelTabProps {
|
||||
channel: LocalChannelNode;
|
||||
}
|
||||
|
||||
export const ChannelTab = ({ channel }: ChannelTabProps) => {
|
||||
const name =
|
||||
channel.attributes.name && channel.attributes.name.length > 0
|
||||
? channel.attributes.name
|
||||
: 'Unnamed';
|
||||
|
||||
return (
|
||||
<Tab
|
||||
id={channel.id}
|
||||
avatar={channel.attributes.avatar}
|
||||
name={name}
|
||||
/>
|
||||
);
|
||||
};
|
||||
34
packages/ui/src/components/chats/chat-tab.tsx
Normal file
34
packages/ui/src/components/chats/chat-tab.tsx
Normal file
@@ -0,0 +1,34 @@
|
||||
import { LocalChatNode } from '@colanode/client/types';
|
||||
import { Tab } from '@colanode/ui/components/layouts/tabs/tab';
|
||||
import { useWorkspace } from '@colanode/ui/contexts/workspace';
|
||||
import { useLiveQuery } from '@colanode/ui/hooks/use-live-query';
|
||||
|
||||
interface ChatTabProps {
|
||||
chat: LocalChatNode;
|
||||
}
|
||||
|
||||
export const ChatTab = ({ chat }: ChatTabProps) => {
|
||||
const workspace = useWorkspace();
|
||||
|
||||
const userId =
|
||||
chat.type === 'chat'
|
||||
? (Object.keys(chat.attributes.collaborators).find(
|
||||
(id) => id !== workspace.userId
|
||||
) ?? '')
|
||||
: '';
|
||||
|
||||
const userGetQuery = useLiveQuery({
|
||||
type: 'user.get',
|
||||
accountId: workspace.accountId,
|
||||
workspaceId: workspace.id,
|
||||
userId,
|
||||
});
|
||||
|
||||
if (userGetQuery.isPending || !userGetQuery.data) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const user = userGetQuery.data;
|
||||
|
||||
return <Tab id={user.id} avatar={user.avatar} name={user.name} />;
|
||||
};
|
||||
21
packages/ui/src/components/databases/database-tab.tsx
Normal file
21
packages/ui/src/components/databases/database-tab.tsx
Normal file
@@ -0,0 +1,21 @@
|
||||
import { LocalDatabaseNode } from '@colanode/client/types';
|
||||
import { Tab } from '@colanode/ui/components/layouts/tabs/tab';
|
||||
|
||||
interface DatabaseTabProps {
|
||||
database: LocalDatabaseNode;
|
||||
}
|
||||
|
||||
export const DatabaseTab = ({ database }: DatabaseTabProps) => {
|
||||
const name =
|
||||
database.attributes.name && database.attributes.name.length > 0
|
||||
? database.attributes.name
|
||||
: 'Untitled';
|
||||
|
||||
return (
|
||||
<Tab
|
||||
id={database.id}
|
||||
avatar={database.attributes.avatar}
|
||||
name={name}
|
||||
/>
|
||||
);
|
||||
};
|
||||
20
packages/ui/src/components/files/file-tab.tsx
Normal file
20
packages/ui/src/components/files/file-tab.tsx
Normal file
@@ -0,0 +1,20 @@
|
||||
import { LocalFileNode } from '@colanode/client/types';
|
||||
import { FileThumbnail } from '@colanode/ui/components/files/file-thumbnail';
|
||||
|
||||
interface FileTabProps {
|
||||
file: LocalFileNode;
|
||||
}
|
||||
|
||||
export const FileTab = ({ file }: FileTabProps) => {
|
||||
const name =
|
||||
file.attributes.name && file.attributes.name.length > 0
|
||||
? file.attributes.name
|
||||
: 'Untitled';
|
||||
|
||||
return (
|
||||
<div className="flex items-center space-x-2">
|
||||
<FileThumbnail file={file} className="size-4 rounded object-contain" />
|
||||
<span>{name}</span>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
17
packages/ui/src/components/folders/folder-tab.tsx
Normal file
17
packages/ui/src/components/folders/folder-tab.tsx
Normal file
@@ -0,0 +1,17 @@
|
||||
import { LocalFolderNode } from '@colanode/client/types';
|
||||
import { Tab } from '@colanode/ui/components/layouts/tabs/tab';
|
||||
|
||||
interface FolderTabProps {
|
||||
folder: LocalFolderNode;
|
||||
}
|
||||
|
||||
export const FolderTab = ({ folder }: FolderTabProps) => {
|
||||
const name =
|
||||
folder.attributes.name && folder.attributes.name.length > 0
|
||||
? folder.attributes.name
|
||||
: 'Untitled';
|
||||
|
||||
return (
|
||||
<Tab id={folder.id} avatar={folder.attributes.avatar} name={name} />
|
||||
);
|
||||
};
|
||||
17
packages/ui/src/components/layouts/layout-add-tab-button.tsx
Normal file
17
packages/ui/src/components/layouts/layout-add-tab-button.tsx
Normal file
@@ -0,0 +1,17 @@
|
||||
import { Plus } from 'lucide-react';
|
||||
|
||||
interface LayoutAddTabButtonProps {
|
||||
onAddTab: () => void;
|
||||
}
|
||||
|
||||
export const LayoutAddTabButton = ({ onAddTab }: LayoutAddTabButtonProps) => {
|
||||
return (
|
||||
<button
|
||||
onClick={onAddTab}
|
||||
className="flex items-center justify-center w-10 h-10 bg-sidebar hover:bg-sidebar-accent transition-all duration-200 app-no-drag-region flex-shrink-0 border-l border-border/30 hover:border-border/60 rounded-tl-md"
|
||||
title="Add new tab"
|
||||
>
|
||||
<Plus className="size-4 text-muted-foreground hover:text-foreground transition-colors" />
|
||||
</button>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,31 @@
|
||||
import { useMemo } from 'react';
|
||||
|
||||
import { router } from '@colanode/ui/router';
|
||||
|
||||
interface TabBarContentProps {
|
||||
location: string;
|
||||
router: typeof router;
|
||||
}
|
||||
|
||||
export const LayoutTabBarContent = ({
|
||||
location,
|
||||
router,
|
||||
}: TabBarContentProps) => {
|
||||
const tabComponent = useMemo(() => {
|
||||
const matches = router.matchRoutes(location);
|
||||
for (let i = matches.length - 1; i >= 0; i--) {
|
||||
const match = matches[i];
|
||||
if (match?.context && 'tab' in match.context) {
|
||||
return match.context.tab;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}, [location]);
|
||||
|
||||
return (
|
||||
<div className="truncate text-sm font-medium">
|
||||
{tabComponent || 'New tab'}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
73
packages/ui/src/components/layouts/layout-tab-bar.tsx
Normal file
73
packages/ui/src/components/layouts/layout-tab-bar.tsx
Normal file
@@ -0,0 +1,73 @@
|
||||
import { X } from 'lucide-react';
|
||||
|
||||
import { Tab } from '@colanode/client/types';
|
||||
import { LayoutTabBarContent } from '@colanode/ui/components/layouts/layout-tab-bar-content';
|
||||
import { cn } from '@colanode/ui/lib/utils';
|
||||
import { router } from '@colanode/ui/router';
|
||||
|
||||
interface LayoutTabBarProps {
|
||||
tab: Tab;
|
||||
router: typeof router;
|
||||
index: number;
|
||||
isActive: boolean;
|
||||
isLast: boolean;
|
||||
onClick: () => void;
|
||||
onDelete: () => void;
|
||||
}
|
||||
|
||||
export const LayoutTabBar = ({
|
||||
tab,
|
||||
router,
|
||||
index,
|
||||
isActive,
|
||||
isLast,
|
||||
onClick,
|
||||
onDelete,
|
||||
}: LayoutTabBarProps) => {
|
||||
return (
|
||||
<div
|
||||
key={tab.id}
|
||||
className={cn(
|
||||
'relative group/tab app-no-drag-region flex items-center gap-2 px-4 py-2 cursor-pointer transition-all duration-200 min-w-[120px] max-w-[240px] flex-1',
|
||||
// Active tab styling with proper z-index for overlapping
|
||||
isActive
|
||||
? 'bg-background text-foreground z-20 shadow-[0_-2px_8px_rgba(0,0,0,0.1),0_2px_4px_rgba(0,0,0,0.05)] border-t border-l border-r border-border'
|
||||
: 'bg-sidebar-accent/60 text-muted-foreground hover:bg-sidebar-accent hover:text-foreground z-10 hover:z-15 shadow-[0_1px_3px_rgba(0,0,0,0.1)]',
|
||||
// Add overlap effect - each tab overlaps the previous one
|
||||
index > 0 && '-ml-3',
|
||||
// Ensure proper stacking order
|
||||
`relative`
|
||||
)}
|
||||
style={{
|
||||
clipPath: isActive
|
||||
? 'polygon(12px 0%, calc(100% - 12px) 0%, 100% 100%, 0% 100%)'
|
||||
: 'polygon(12px 0%, calc(100% - 12px) 0%, calc(100% - 6px) 100%, 6px 100%)',
|
||||
}}
|
||||
onClick={onClick}
|
||||
>
|
||||
{/* Tab content */}
|
||||
<div className="flex items-center gap-2 flex-1 min-w-0 z-10">
|
||||
<LayoutTabBarContent location={tab.location} router={router} />
|
||||
<button
|
||||
className={cn(
|
||||
'opacity-0 group-hover/tab:opacity-100 transition-all duration-200 flex-shrink-0 rounded-full p-1 hover:bg-destructive/20 hover:text-destructive',
|
||||
isActive && 'opacity-70 hover:opacity-100',
|
||||
'ml-auto' // Push to the right edge
|
||||
)}
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
onDelete();
|
||||
}}
|
||||
title="Close tab"
|
||||
>
|
||||
<X className="size-3" />
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{/* Browser-like tab separator */}
|
||||
{!isActive && !isLast && (
|
||||
<div className="absolute right-0 top-2 bottom-2 w-px bg-border/50" />
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -1,46 +0,0 @@
|
||||
import {
|
||||
createMemoryHistory,
|
||||
createRouter,
|
||||
RouterProvider,
|
||||
} from '@tanstack/react-router';
|
||||
import { useMemo } from 'react';
|
||||
|
||||
import { routeTree } from '@colanode/ui/router';
|
||||
|
||||
interface LayoutTabContentProps {
|
||||
location: string;
|
||||
onChange: (location: string) => void;
|
||||
}
|
||||
|
||||
export const LayoutTabContent = ({
|
||||
location,
|
||||
onChange,
|
||||
}: LayoutTabContentProps) => {
|
||||
const router = useMemo(() => {
|
||||
const history = createMemoryHistory({
|
||||
initialEntries: [location],
|
||||
});
|
||||
|
||||
const router = createRouter({
|
||||
routeTree,
|
||||
context: {},
|
||||
history,
|
||||
defaultPreload: 'intent',
|
||||
scrollRestoration: true,
|
||||
defaultStructuralSharing: true,
|
||||
defaultPreloadStaleTime: 0,
|
||||
});
|
||||
|
||||
router.subscribe('onRendered', (event) => {
|
||||
if (!event.hrefChanged) {
|
||||
return;
|
||||
}
|
||||
|
||||
onChange(event.toLocation.href);
|
||||
});
|
||||
|
||||
return router;
|
||||
}, []);
|
||||
|
||||
return <RouterProvider router={router} />;
|
||||
};
|
||||
@@ -1,11 +0,0 @@
|
||||
interface LayoutTabProps {
|
||||
location: string;
|
||||
}
|
||||
|
||||
const LayoutTabContent = ({ location }: LayoutTabProps) => {
|
||||
return <div>LayoutTabContent</div>;
|
||||
};
|
||||
|
||||
export const LayoutTab = ({ location }: LayoutTabProps) => {
|
||||
return <div>LayoutTab</div>;
|
||||
};
|
||||
@@ -1,5 +1,9 @@
|
||||
import { Plus, X } from 'lucide-react';
|
||||
import { useCallback } from 'react';
|
||||
import {
|
||||
createMemoryHistory,
|
||||
createRouter,
|
||||
RouterProvider,
|
||||
} from '@tanstack/react-router';
|
||||
import { useCallback, useRef } from 'react';
|
||||
|
||||
import { Tab } from '@colanode/client/types';
|
||||
import {
|
||||
@@ -8,18 +12,56 @@ import {
|
||||
generateId,
|
||||
IdType,
|
||||
} from '@colanode/core';
|
||||
import { LayoutTabContent } from '@colanode/ui/components/layouts/layout-tab-content';
|
||||
import { LayoutAddTabButton } from '@colanode/ui/components/layouts/layout-add-tab-button';
|
||||
import { LayoutTabBar } from '@colanode/ui/components/layouts/layout-tab-bar';
|
||||
import { cn } from '@colanode/ui/lib/utils';
|
||||
import { router, routeTree } from '@colanode/ui/router';
|
||||
import { useAppStore } from '@colanode/ui/stores/app';
|
||||
|
||||
export const LayoutTabs = () => {
|
||||
const allTabs = useAppStore((state) => state.tabs);
|
||||
const activeTabId = useAppStore((state) => state.metadata.tab);
|
||||
|
||||
const tabs = Object.values(allTabs).sort((a, b) =>
|
||||
compareString(a.index, b.index)
|
||||
);
|
||||
const activeTab = tabs.find((tab) => tab.id === activeTabId) ?? tabs[0]!;
|
||||
const routers = useRef<Record<string, typeof router>>(
|
||||
tabs.reduce(
|
||||
(acc, tab) => {
|
||||
const router = createRouter({
|
||||
routeTree,
|
||||
context: {},
|
||||
history: createMemoryHistory({ initialEntries: [tab.location] }),
|
||||
defaultPreload: 'intent',
|
||||
scrollRestoration: true,
|
||||
defaultStructuralSharing: false,
|
||||
defaultPreloadStaleTime: 0,
|
||||
});
|
||||
|
||||
router.subscribe('onRendered', (event) => {
|
||||
if (!event.hrefChanged) {
|
||||
return;
|
||||
}
|
||||
|
||||
const location = event.toLocation.href;
|
||||
useAppStore.getState().upsertTab({
|
||||
...tab,
|
||||
location,
|
||||
});
|
||||
|
||||
window.colanode.executeMutation({
|
||||
type: 'tab.update',
|
||||
id: tab.id,
|
||||
location,
|
||||
});
|
||||
});
|
||||
|
||||
acc[tab.id] = router;
|
||||
return acc;
|
||||
},
|
||||
{} as Record<string, typeof router>
|
||||
)
|
||||
);
|
||||
|
||||
const deleteTab = useCallback((tabId: string) => {
|
||||
const currentTabs = useAppStore.getState().tabs;
|
||||
@@ -77,99 +119,46 @@ export const LayoutTabs = () => {
|
||||
});
|
||||
}, []);
|
||||
|
||||
const updateTabLocation = useCallback((tabId: string, location: string) => {
|
||||
const allTabs = useAppStore.getState().tabs;
|
||||
const tab = allTabs[tabId];
|
||||
if (!tab) {
|
||||
return;
|
||||
}
|
||||
|
||||
useAppStore.getState().upsertTab({
|
||||
...tab,
|
||||
location,
|
||||
});
|
||||
|
||||
window.colanode.executeMutation({
|
||||
type: 'tab.update',
|
||||
id: tabId,
|
||||
location,
|
||||
});
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<div className="flex flex-col h-full">
|
||||
{/* Tab bar with browser-like styling */}
|
||||
<div className="relative flex bg-sidebar border-b border-border h-10 overflow-hidden">
|
||||
{tabs.map((tab, index) => {
|
||||
const isActive = tab.id === activeTab.id;
|
||||
return (
|
||||
<div
|
||||
key={tab.id}
|
||||
className={cn(
|
||||
'relative group/tab app-no-drag-region flex items-center gap-2 px-4 py-2 cursor-pointer transition-all duration-200 min-w-[120px] max-w-[240px] flex-1',
|
||||
// Active tab styling with proper z-index for overlapping
|
||||
isActive
|
||||
? 'bg-background text-foreground z-20 shadow-[0_-2px_8px_rgba(0,0,0,0.1),0_2px_4px_rgba(0,0,0,0.05)] border-t border-l border-r border-border'
|
||||
: 'bg-sidebar-accent/60 text-muted-foreground hover:bg-sidebar-accent hover:text-foreground z-10 hover:z-15 shadow-[0_1px_3px_rgba(0,0,0,0.1)]',
|
||||
// Add overlap effect - each tab overlaps the previous one
|
||||
index > 0 && '-ml-3',
|
||||
// Ensure proper stacking order
|
||||
`relative`
|
||||
)}
|
||||
style={{
|
||||
clipPath: isActive
|
||||
? 'polygon(12px 0%, calc(100% - 12px) 0%, 100% 100%, 0% 100%)'
|
||||
: 'polygon(12px 0%, calc(100% - 12px) 0%, calc(100% - 6px) 100%, 6px 100%)',
|
||||
}}
|
||||
onClick={() => switchTab(tab.id)}
|
||||
>
|
||||
{/* Tab content */}
|
||||
<div className="flex items-center gap-2 flex-1 min-w-0 z-10">
|
||||
<div className="truncate text-sm font-medium">
|
||||
Tab {index + 1}
|
||||
</div>
|
||||
<button
|
||||
className={cn(
|
||||
'opacity-0 group-hover/tab:opacity-100 transition-all duration-200 flex-shrink-0 rounded-full p-1 hover:bg-destructive/20 hover:text-destructive',
|
||||
isActive && 'opacity-70 hover:opacity-100',
|
||||
'ml-auto' // Push to the right edge
|
||||
)}
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
deleteTab(tab.id);
|
||||
}}
|
||||
title="Close tab"
|
||||
>
|
||||
<X className="size-3" />
|
||||
</button>
|
||||
</div>
|
||||
const isLast =
|
||||
index === tabs.length - 1 || tabs[index + 1]?.id === activeTab.id;
|
||||
|
||||
{/* Browser-like tab separator */}
|
||||
{!isActive &&
|
||||
index < tabs.length - 1 &&
|
||||
tabs[index + 1]?.id !== activeTab.id && (
|
||||
<div className="absolute right-0 top-2 bottom-2 w-px bg-border/50" />
|
||||
)}
|
||||
</div>
|
||||
const router = routers.current[tab.id];
|
||||
if (!router) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<LayoutTabBar
|
||||
key={tab.id}
|
||||
tab={tab}
|
||||
router={router}
|
||||
index={index}
|
||||
isActive={isActive}
|
||||
isLast={isLast}
|
||||
onClick={() => switchTab(tab.id)}
|
||||
onDelete={() => deleteTab(tab.id)}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
|
||||
{/* Add tab button */}
|
||||
<button
|
||||
onClick={addTab}
|
||||
className="flex items-center justify-center w-10 h-10 bg-sidebar hover:bg-sidebar-accent transition-all duration-200 app-no-drag-region flex-shrink-0 border-l border-border/30 hover:border-border/60 rounded-tl-md"
|
||||
title="Add new tab"
|
||||
>
|
||||
<Plus className="size-4 text-muted-foreground hover:text-foreground transition-colors" />
|
||||
</button>
|
||||
|
||||
{/* Tab bar background with subtle gradient */}
|
||||
<LayoutAddTabButton onAddTab={addTab} />
|
||||
<div className="absolute inset-0 pointer-events-none bg-gradient-to-b from-background/5 to-border/10" />
|
||||
</div>
|
||||
|
||||
<div className="flex-1 overflow-hidden relative">
|
||||
{tabs.map((tab) => {
|
||||
const isActive = tab.id === activeTab.id;
|
||||
|
||||
const router = routers.current[tab.id];
|
||||
if (!router) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<div
|
||||
key={tab.id}
|
||||
@@ -181,10 +170,7 @@ export const LayoutTabs = () => {
|
||||
pointerEvents: isActive ? 'auto' : 'none',
|
||||
}}
|
||||
>
|
||||
<LayoutTabContent
|
||||
location={tab.location}
|
||||
onChange={(location) => updateTabLocation(tab.id, location)}
|
||||
/>
|
||||
<RouterProvider router={router} />
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
|
||||
16
packages/ui/src/components/layouts/tabs/tab.tsx
Normal file
16
packages/ui/src/components/layouts/tabs/tab.tsx
Normal file
@@ -0,0 +1,16 @@
|
||||
import { Avatar } from '@colanode/ui/components/avatars/avatar';
|
||||
|
||||
interface TabProps {
|
||||
id: string;
|
||||
avatar?: string | null;
|
||||
name: string;
|
||||
}
|
||||
|
||||
export const Tab = ({ id, avatar, name }: TabProps) => {
|
||||
return (
|
||||
<div className="flex items-center space-x-2">
|
||||
<Avatar id={id} avatar={avatar} name={name} className="size-4" />
|
||||
<span>{name}</span>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
16
packages/ui/src/components/messages/message-tab.tsx
Normal file
16
packages/ui/src/components/messages/message-tab.tsx
Normal file
@@ -0,0 +1,16 @@
|
||||
import { MessageCircle } from 'lucide-react';
|
||||
|
||||
import { LocalMessageNode } from '@colanode/client/types';
|
||||
|
||||
interface MessageTabProps {
|
||||
message: LocalMessageNode;
|
||||
}
|
||||
|
||||
export const MessageTab = ({ message }: MessageTabProps) => {
|
||||
return (
|
||||
<div className="flex items-center space-x-2" id={message.id}>
|
||||
<MessageCircle className="size-4" />
|
||||
<span>Message</span>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
57
packages/ui/src/components/nodes/node-tab.tsx
Normal file
57
packages/ui/src/components/nodes/node-tab.tsx
Normal file
@@ -0,0 +1,57 @@
|
||||
import { ChannelTab } from '@colanode/ui/components/channels/channel-tab';
|
||||
import { ChatTab } from '@colanode/ui/components/chats/chat-tab';
|
||||
import { DatabaseTab } from '@colanode/ui/components/databases/database-tab';
|
||||
import { FileTab } from '@colanode/ui/components/files/file-tab';
|
||||
import { FolderTab } from '@colanode/ui/components/folders/folder-tab';
|
||||
import { MessageTab } from '@colanode/ui/components/messages/message-tab';
|
||||
import { PageTab } from '@colanode/ui/components/pages/page-tab';
|
||||
import { RecordTab } from '@colanode/ui/components/records/record-tab';
|
||||
import { SpaceTab } from '@colanode/ui/components/spaces/space-tab';
|
||||
import { useLiveQuery } from '@colanode/ui/hooks/use-live-query';
|
||||
|
||||
interface NodeTabProps {
|
||||
accountId: string;
|
||||
workspaceId: string;
|
||||
nodeId: string;
|
||||
}
|
||||
|
||||
export const NodeTab = ({ accountId, workspaceId, nodeId }: NodeTabProps) => {
|
||||
const query = useLiveQuery({
|
||||
type: 'node.get',
|
||||
accountId,
|
||||
workspaceId,
|
||||
nodeId,
|
||||
});
|
||||
|
||||
if (query.isPending) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const node = query.data;
|
||||
if (!node) {
|
||||
return null;
|
||||
}
|
||||
|
||||
switch (node.type) {
|
||||
case 'space':
|
||||
return <SpaceTab space={node} />;
|
||||
case 'channel':
|
||||
return <ChannelTab channel={node} />;
|
||||
case 'chat':
|
||||
return <ChatTab chat={node} />;
|
||||
case 'page':
|
||||
return <PageTab page={node} />;
|
||||
case 'database':
|
||||
return <DatabaseTab database={node} />;
|
||||
case 'record':
|
||||
return <RecordTab record={node} />;
|
||||
case 'folder':
|
||||
return <FolderTab folder={node} />;
|
||||
case 'file':
|
||||
return <FileTab file={node} />;
|
||||
case 'message':
|
||||
return <MessageTab message={node} />;
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
};
|
||||
17
packages/ui/src/components/pages/page-tab.tsx
Normal file
17
packages/ui/src/components/pages/page-tab.tsx
Normal file
@@ -0,0 +1,17 @@
|
||||
import { LocalPageNode } from '@colanode/client/types';
|
||||
import { Tab } from '@colanode/ui/components/layouts/tabs/tab';
|
||||
|
||||
interface PageTabProps {
|
||||
page: LocalPageNode;
|
||||
}
|
||||
|
||||
export const PageTab = ({ page }: PageTabProps) => {
|
||||
const name =
|
||||
page.attributes.name && page.attributes.name.length > 0
|
||||
? page.attributes.name
|
||||
: 'Untitled';
|
||||
|
||||
return (
|
||||
<Tab id={page.id} avatar={page.attributes.avatar} name={name} />
|
||||
);
|
||||
};
|
||||
17
packages/ui/src/components/records/record-tab.tsx
Normal file
17
packages/ui/src/components/records/record-tab.tsx
Normal file
@@ -0,0 +1,17 @@
|
||||
import { LocalRecordNode } from '@colanode/client/types';
|
||||
import { Tab } from '@colanode/ui/components/layouts/tabs/tab';
|
||||
|
||||
interface RecordTabProps {
|
||||
record: LocalRecordNode;
|
||||
}
|
||||
|
||||
export const RecordTab = ({ record }: RecordTabProps) => {
|
||||
const name =
|
||||
record.attributes.name && record.attributes.name.length > 0
|
||||
? record.attributes.name
|
||||
: 'Untitled';
|
||||
|
||||
return (
|
||||
<Tab id={record.id} avatar={record.attributes.avatar} name={name} />
|
||||
);
|
||||
};
|
||||
16
packages/ui/src/components/spaces/space-tab.tsx
Normal file
16
packages/ui/src/components/spaces/space-tab.tsx
Normal file
@@ -0,0 +1,16 @@
|
||||
import { LocalSpaceNode } from '@colanode/client/types';
|
||||
import { Tab } from '@colanode/ui/components/layouts/tabs/tab';
|
||||
|
||||
interface SpaceTabProps {
|
||||
space: LocalSpaceNode;
|
||||
}
|
||||
|
||||
export const SpaceTab = ({ space }: SpaceTabProps) => {
|
||||
return (
|
||||
<Tab
|
||||
id={space.id}
|
||||
avatar={space.attributes.avatar}
|
||||
name={space.attributes.name}
|
||||
/>
|
||||
);
|
||||
};
|
||||
@@ -1,7 +1,21 @@
|
||||
import { Tab } from '@colanode/ui/components/layouts/tabs/tab';
|
||||
import { useAppStore } from '@colanode/ui/stores/app';
|
||||
|
||||
interface WorkspaceTabProps {
|
||||
accountId: string;
|
||||
workspaceId: string;
|
||||
}
|
||||
|
||||
export const WorkspaceTab = ({ workspaceId }: WorkspaceTabProps) => {
|
||||
return <div>WorkspaceTab {workspaceId}</div>;
|
||||
export const WorkspaceTab = ({ accountId, workspaceId }: WorkspaceTabProps) => {
|
||||
const workspace = useAppStore(
|
||||
(state) => state.accounts[accountId]?.workspaces[workspaceId]
|
||||
);
|
||||
|
||||
if (!workspace) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<Tab id={workspace.id} avatar={workspace.avatar} name={workspace.name} />
|
||||
);
|
||||
};
|
||||
|
||||
@@ -14,6 +14,7 @@ import { LoginScreen } from '@colanode/ui/components/accounts/login-screen';
|
||||
import { AppAppearanceSettingsScreen } from '@colanode/ui/components/app/app-appearance-settings-screen';
|
||||
import { NodeErrorScreen } from '@colanode/ui/components/nodes/node-error-screen';
|
||||
import { NodeScreen } from '@colanode/ui/components/nodes/node-screen';
|
||||
import { NodeTab } from '@colanode/ui/components/nodes/node-tab';
|
||||
import { WorkspaceDownloadsScreen } from '@colanode/ui/components/workspaces/downloads/workspace-downloads-screen';
|
||||
import { WorkspaceStorageScreen } from '@colanode/ui/components/workspaces/storage/workspace-storage-screen';
|
||||
import { WorkspaceUploadsScreen } from '@colanode/ui/components/workspaces/uploads/workspace-uploads-screen';
|
||||
@@ -143,7 +144,12 @@ export const workspaceRoute = createRoute({
|
||||
component: WorkspaceScreen,
|
||||
context: (ctx) => {
|
||||
return {
|
||||
tab: <WorkspaceTab workspaceId={ctx.params.workspaceId} />,
|
||||
tab: (
|
||||
<WorkspaceTab
|
||||
accountId={ctx.params.accountId}
|
||||
workspaceId={ctx.params.workspaceId}
|
||||
/>
|
||||
),
|
||||
};
|
||||
},
|
||||
});
|
||||
@@ -252,6 +258,17 @@ export const nodeRoute = createRoute({
|
||||
path: '/$nodeId',
|
||||
component: NodeScreen,
|
||||
errorComponent: NodeErrorScreen,
|
||||
context: (ctx) => {
|
||||
return {
|
||||
tab: (
|
||||
<NodeTab
|
||||
accountId={ctx.params.accountId}
|
||||
workspaceId={ctx.params.workspaceId}
|
||||
nodeId={ctx.params.nodeId}
|
||||
/>
|
||||
),
|
||||
};
|
||||
},
|
||||
});
|
||||
|
||||
export const nodeMaskRoute = createRoute({
|
||||
|
||||
Reference in New Issue
Block a user