diff --git a/apps/client/src/features/space/components/sidebar/space-sidebar.tsx b/apps/client/src/features/space/components/sidebar/space-sidebar.tsx
index 51e529ba..fef03583 100644
--- a/apps/client/src/features/space/components/sidebar/space-sidebar.tsx
+++ b/apps/client/src/features/space/components/sidebar/space-sidebar.tsx
@@ -14,7 +14,6 @@ import {
IconFileExport,
IconHome,
IconPlus,
- IconSearch,
IconSettings,
IconStar,
IconStarFilled,
@@ -27,7 +26,6 @@ import {
} from "@/features/space/queries/space-watcher-query.ts";
import classes from "./space-sidebar.module.css";
import React from "react";
-import { useAtom } from "jotai";
import { useTreeMutation } from "@/features/page/tree/hooks/use-tree-mutation.ts";
import { Link, useLocation, useParams } from "react-router-dom";
import clsx from "clsx";
@@ -50,17 +48,12 @@ import {
useAddFavoriteMutation,
useRemoveFavoriteMutation,
} from "@/features/favorite/queries/favorite-query";
-import { mobileSidebarAtom } from "@/components/layouts/global/hooks/atoms/sidebar-atom.ts";
-import { useToggleSidebar } from "@/components/layouts/global/hooks/hooks/use-toggle-sidebar.ts";
-import { searchSpotlight } from "@/features/search/constants";
export function SpaceSidebar() {
const { t } = useTranslation();
const location = useLocation();
const [opened, { open: openSettings, close: closeSettings }] =
useDisclosure(false);
- const [mobileSidebarOpened] = useAtom(mobileSidebarAtom);
- const toggleMobileSidebar = useToggleSidebar(mobileSidebarAtom);
const { spaceSlug } = useParams();
const { data: space } = useGetSpaceBySlugQuery(spaceSlug);
@@ -88,18 +81,13 @@ export function SpaceSidebar() {
marginBottom: 3,
}}
>
-
-
-
+
@@ -123,55 +111,6 @@ export function SpaceSidebar() {
{t("Overview")}
-
-
-
-
- {t("Search")}
-
-
-
-
-
-
- {t("Space settings")}
-
-
-
- {spaceAbility.can(
- SpaceCaslAction.Manage,
- SpaceCaslSubject.Page,
- ) && (
- {
- handleCreatePage();
- if (mobileSidebarOpened) {
- toggleMobileSidebar();
- }
- }}
- >
-
-
- {t("New page")}
-
-
- )}
diff --git a/apps/client/src/features/space/components/sidebar/switch-space.module.css b/apps/client/src/features/space/components/sidebar/switch-space.module.css
index 451aa9c7..480c40bb 100644
--- a/apps/client/src/features/space/components/sidebar/switch-space.module.css
+++ b/apps/client/src/features/space/components/sidebar/switch-space.module.css
@@ -1,5 +1,48 @@
-.spaceName {
+.wrapper {
width: 100%;
- padding: var(--mantine-spacing-sm);
+}
+
+.header {
+ padding: rem(4px) var(--mantine-spacing-sm);
+}
+
+.spaceName {
+ flex: 1;
+ min-width: 0;
color: light-dark(var(--mantine-color-dark-4), var(--mantine-color-dark-0));
}
+
+.grid {
+ display: flex;
+ flex-wrap: wrap;
+ gap: rem(6px);
+ padding: rem(4px) var(--mantine-spacing-sm) var(--mantine-spacing-xs);
+ max-height: rem(180px);
+ overflow-y: auto;
+}
+
+.card {
+ display: flex;
+ align-items: center;
+ gap: rem(6px);
+ width: rem(120px);
+ padding: rem(6px) rem(8px);
+ border-radius: var(--mantine-radius-sm);
+ border: rem(1px) solid light-dark(var(--mantine-color-gray-3), var(--mantine-color-dark-4));
+ background-color: light-dark(var(--mantine-color-gray-0), var(--mantine-color-dark-7));
+}
+
+.card:hover {
+ background-color: light-dark(var(--mantine-color-gray-2), var(--mantine-color-dark-6));
+}
+
+.cardActive {
+ border-color: var(--mantine-primary-color-filled);
+ background-color: light-dark(var(--mantine-color-gray-2), var(--mantine-color-dark-6));
+}
+
+.cardName {
+ flex: 1;
+ min-width: 0;
+ color: light-dark(var(--mantine-color-gray-8), var(--mantine-color-dark-0));
+}
diff --git a/apps/client/src/features/space/components/sidebar/switch-space.tsx b/apps/client/src/features/space/components/sidebar/switch-space.tsx
index 7531a592..23b19679 100644
--- a/apps/client/src/features/space/components/sidebar/switch-space.tsx
+++ b/apps/client/src/features/space/components/sidebar/switch-space.tsx
@@ -1,77 +1,115 @@
import classes from "./switch-space.module.css";
import { useNavigate } from "react-router-dom";
-import { SpaceSelect } from "./space-select";
import { getSpaceUrl } from "@/lib/config";
-import { Button, Popover, Text } from "@mantine/core";
-import { IconChevronDown, IconChevronUp } from "@tabler/icons-react";
-import { useDisclosure } from "@mantine/hooks";
+import { ActionIcon, Group, Text, Tooltip, UnstyledButton } from "@mantine/core";
+import { IconSettings } from "@tabler/icons-react";
import { CustomAvatar } from "@/components/ui/custom-avatar.tsx";
import { AvatarIconType } from "@/features/attachments/types/attachment.types.ts";
-import React from "react";
+import {
+ prefetchSpace,
+ useGetSpacesQuery,
+} from "@/features/space/queries/space-query.ts";
+import { ISpace } from "../../types/space.types";
+import { useTranslation } from "react-i18next";
+import clsx from "clsx";
+import React, { useMemo } from "react";
interface SwitchSpaceProps {
+ spaceId: string;
spaceName: string;
spaceSlug: string;
spaceIcon?: string;
+ onSettings: () => void;
}
export function SwitchSpace({
+ spaceId,
spaceName,
spaceSlug,
spaceIcon,
+ onSettings,
}: SwitchSpaceProps) {
+ const { t } = useTranslation();
const navigate = useNavigate();
- const [opened, { close, toggle }] = useDisclosure(false);
+ // Load every space the user belongs to (API caps limit at 100) and render
+ // them as an always-visible grid instead of the previous searchable popover.
+ const { data } = useGetSpacesQuery({ limit: 100 });
- const handleSelect = (value: string) => {
- if (value) {
- navigate(getSpaceUrl(value));
- close();
+ // Sort spaces alphabetically by name for a stable, predictable grid.
+ const spaces = useMemo(() => {
+ const list = [...(data?.items ?? [])];
+ // Ensure the active space is always present (and highlightable) in the grid,
+ // even when it falls outside the first 100 spaces returned by the API.
+ if (spaceSlug && !list.some((s: ISpace) => s.slug === spaceSlug)) {
+ list.push({
+ id: spaceId,
+ name: spaceName,
+ slug: spaceSlug,
+ logo: spaceIcon,
+ } as ISpace);
+ }
+ return list.sort((a: ISpace, b: ISpace) => a.name.localeCompare(b.name));
+ }, [data, spaceId, spaceName, spaceSlug, spaceIcon]);
+
+ const handleSelect = (slug: string) => {
+ if (slug && slug !== spaceSlug) {
+ navigate(getSpaceUrl(slug));
}
};
return (
-
-
- : }
- color="gray"
- onClick={toggle}
- >
-
-
- {spaceName}
-
-
-
-
- handleSelect(space.slug)}
- width={300}
- opened={true}
+
+
+
-
-
+
+ {spaceName}
+
+
+
+
+
+
+
+
+
+ {spaces.map((space: ISpace) => (
+ handleSelect(space.slug)}
+ onMouseEnter={() => prefetchSpace(space.slug, space.id)}
+ title={space.name}
+ >
+
+
+ {space.name}
+
+
+ ))}
+
+
);
}