Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: allow user to control left panel from Chat input #1880

Merged
merged 4 commits into from
Dec 13, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion frontend/app/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { PropsWithChildren, useEffect } from "react";

import { BrainProvider } from "@/lib/context";
import { useBrainContext } from "@/lib/context/BrainProvider/hooks/useBrainContext";
import { SideBarProvider } from "@/lib/context/SidebarProvider/sidebar-provider";
import { useSupabase } from "@/lib/context/SupabaseProvider";
import { UpdateMetadata } from "@/lib/helpers/updateMetadata";
import { usePageTracking } from "@/services/analytics/june/usePageTracking";
Expand Down Expand Up @@ -40,7 +41,9 @@ const AppWithQueryClient = ({ children }: PropsWithChildren): JSX.Element => {
return (
<QueryClientProvider client={queryClient}>
<BrainProvider>
<App>{children}</App>
<SideBarProvider>
<App>{children}</App>
</SideBarProvider>
</BrainProvider>
</QueryClientProvider>
);
Expand Down
5 changes: 4 additions & 1 deletion frontend/app/chat/[chatId]/__tests__/page.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import {
ChatProviderMock,
} from "@/lib/context/ChatProvider/mocks/ChatProviderMock";
import { KnowledgeToFeedProvider } from "@/lib/context/KnowledgeToFeedProvider";
import { SideBarProvider } from "@/lib/context/SidebarProvider/sidebar-provider";
import {
SupabaseContextMock,
SupabaseProviderMock,
Expand Down Expand Up @@ -87,7 +88,9 @@ describe("Chat page", () => {
<ChatProviderMock>
<SupabaseProviderMock>
<BrainProviderMock>
<SelectedChatPage />,
<SideBarProvider>
<SelectedChatPage />,
</SideBarProvider>
</BrainProviderMock>
</SupabaseProviderMock>
</ChatProviderMock>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import { useTranslation } from "react-i18next";
import { LuPanelLeftClose, LuPanelRightClose } from "react-icons/lu";

import Button from "@/lib/components/ui/Button";
import { useSideBarContext } from "@/lib/context/SidebarProvider/hooks/useSideBarContext";

export const MenuControlButton = (): JSX.Element => {
const { isOpened, setIsOpened } = useSideBarContext();
const Icon = isOpened ? LuPanelLeftClose : LuPanelRightClose;
const { t } = useTranslation("chat");

return (
<Button
variant="tertiary"
className="px-2 py-0"
type="button"
onClick={() => setIsOpened(!isOpened)}
>
<div className="flex flex-col items-center justify-center gap-1">
<Icon className="text-2xl md:text-3xl self-center text-accent" />
<span className="text-xs">{t("menu")}</span>
</div>
</Button>
);
};
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import { getBrainIconFromBrainType } from "@/lib/helpers/getBrainIconFromBrainTy
import { OnboardingQuestions } from "./components";
import { ActionsModal } from "./components/ActionsModal/ActionsModal";
import { ChatEditor } from "./components/ChatEditor/ChatEditor";
import { MenuControlButton } from "./components/MenuControlButton";
import { useChatInput } from "./hooks/useChatInput";

type ChatInputProps = {
Expand Down Expand Up @@ -37,6 +38,7 @@ export const ChatInput = ({
}}
className="sticky bottom-0 bg-white dark:bg-black w-full flex items-center gap-2 z-20 p-2"
>
<MenuControlButton />
{!shouldDisplayFeedOrSecretsCard && (
<Button
className="p-0"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import {
ChatProviderMock,
} from "@/lib/context/ChatProvider/mocks/ChatProviderMock";
import { KnowledgeToFeedProvider } from "@/lib/context/KnowledgeToFeedProvider";
import { SideBarProvider } from "@/lib/context/SidebarProvider/sidebar-provider";
import { SupabaseContextMock } from "@/lib/context/SupabaseProvider/mocks/SupabaseProviderMock";

vi.mock("@/lib/context/SupabaseProvider/supabase-provider", () => ({
Expand Down Expand Up @@ -91,7 +92,9 @@ describe("ChatsList", () => {
<KnowledgeToFeedProvider>
<ChatProviderMock>
<BrainProviderMock>
<ChatsList />
<SideBarProvider>
<ChatsList />
</SideBarProvider>
</BrainProviderMock>
</ChatProviderMock>
</KnowledgeToFeedProvider>
Expand All @@ -110,7 +113,9 @@ describe("ChatsList", () => {
<KnowledgeToFeedProvider>
<ChatProviderMock>
<BrainProviderMock>
<ChatsList />
<SideBarProvider>
<ChatsList />
</SideBarProvider>
</BrainProviderMock>
</ChatProviderMock>
</KnowledgeToFeedProvider>
Expand All @@ -133,7 +138,9 @@ describe("ChatsList", () => {
<KnowledgeToFeedProvider>
<ChatProviderMock>
<BrainProviderMock>
<ChatsList />
<SideBarProvider>
<ChatsList />
</SideBarProvider>
</BrainProviderMock>
</ChatProviderMock>
</KnowledgeToFeedProvider>
Expand Down
34 changes: 13 additions & 21 deletions frontend/lib/components/Sidebar/Sidebar.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,8 @@
import { motion, MotionConfig } from "framer-motion";
import { usePathname } from "next/navigation";
import { useEffect, useState } from "react";
import { LuPanelLeftOpen } from "react-icons/lu";

import { SidebarHeader } from "@/lib/components/Sidebar/components/SidebarHeader";
import { useDevice } from "@/lib/hooks/useDevice";
import { useSideBarContext } from "@/lib/context/SidebarProvider/hooks/useSideBarContext";
import { cn } from "@/lib/utils";

import {
Expand All @@ -21,13 +19,7 @@ export const Sidebar = ({
children,
showButtons,
}: SidebarProps): JSX.Element => {
const { isMobile } = useDevice();
const pathname = usePathname();
const [open, setOpen] = useState(!isMobile);

useEffect(() => {
setOpen(!isMobile);
}, [isMobile, pathname]);
const { isOpened, setIsOpened } = useSideBarContext();

return (
<MotionConfig transition={{ mass: 1, damping: 10, duration: 0.2 }}>
Expand All @@ -36,40 +28,40 @@ export const Sidebar = ({
dragConstraints={{ right: 0, left: 0 }}
dragElastic={0.15}
onDragEnd={(event, info) => {
if (info.offset.x > 100 && !open) {
setOpen(true);
} else if (info.offset.x < -100 && open) {
setOpen(false);
if (info.offset.x > 100 && !isOpened) {
setIsOpened(true);
} else if (info.offset.x < -100 && isOpened) {
setIsOpened(false);
}
}}
className="flex flex-col fixed sm:sticky top-0 left-0 h-full overflow-visible z-30 border-r border-black/10 dark:border-white/25 bg-white dark:bg-black"
>
{!open && (
{!isOpened && (
<button
title="Open Sidebar"
type="button"
className="absolute p-3 text-2xl bg-red top-5 -right-20 hover:text-primary dark:hover:text-gray-200 transition-colors"
data-testid="open-sidebar-button"
onClick={() => setOpen(true)}
onClick={() => setIsOpened(true)}
>
<LuPanelLeftOpen />
</button>
)}
<motion.div
initial={{
width: open ? "18rem" : "0px",
width: isOpened ? "18rem" : "0px",
}}
animate={{
width: open ? "18rem" : "0px",
opacity: open ? 1 : 0.5,
boxShadow: open
width: isOpened ? "18rem" : "0px",
opacity: isOpened ? 1 : 0.5,
boxShadow: isOpened
? "10px 10px 16px rgba(0, 0, 0, 0)"
: "10px 10px 16px rgba(0, 0, 0, 0.5)",
}}
className={cn("overflow-hidden flex flex-col flex-1 max-w-xs")}
data-testid="sidebar"
>
<SidebarHeader setOpen={setOpen} />
<SidebarHeader />
<div className="overflow-auto flex flex-col flex-1">{children}</div>
{showButtons && <SidebarFooter showButtons={showButtons} />}
</motion.div>
Expand Down
9 changes: 6 additions & 3 deletions frontend/lib/components/Sidebar/__tests__/Sidebar.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,16 +9,19 @@ import {
import { afterEach, describe, expect, it, vi } from "vitest";

import { Sidebar } from "@/lib/components/Sidebar/Sidebar";
import { SideBarProvider } from "@/lib/context/SidebarProvider/sidebar-provider";
import { useDevice } from "@/lib/hooks/useDevice";

vi.mock("@/lib/hooks/useDevice");

const renderSidebar = async () => {
await act(() =>
render(
<Sidebar>
<div data-testid="sidebar-test-content">📦</div>
</Sidebar>
<SideBarProvider>
<Sidebar>
<div data-testid="sidebar-test-content">📦</div>
</Sidebar>
</SideBarProvider>
)
);
};
Expand Down
14 changes: 6 additions & 8 deletions frontend/lib/components/Sidebar/components/SidebarHeader.tsx
Original file line number Diff line number Diff line change
@@ -1,13 +1,11 @@
import { Dispatch, SetStateAction } from "react";
import { LuPanelLeft } from "react-icons/lu";
import { LuPanelLeftClose } from "react-icons/lu";

import { Logo } from "@/lib/components/Logo/Logo";
import { useSideBarContext } from "@/lib/context/SidebarProvider/hooks/useSideBarContext";

type SidebarProps = {
setOpen: Dispatch<SetStateAction<boolean>>;
};
export const SidebarHeader = (): JSX.Element => {
const { setIsOpened } = useSideBarContext();

export const SidebarHeader = ({ setOpen }: SidebarProps): JSX.Element => {
return (
<div className="p-2 border-b relative">
<div className="max-w-screen-xl flex justify-between items-center pt-3 pl-3">
Expand All @@ -17,9 +15,9 @@ export const SidebarHeader = ({ setOpen }: SidebarProps): JSX.Element => {
className="p-3 text-2xl bg:white dark:bg-black text-black dark:text-white hover:text-primary dark:hover:text-gray-200 transition-colors"
type="button"
data-testid="close-sidebar-button"
onClick={() => setOpen(false)}
onClick={() => setIsOpened(false)}
>
<LuPanelLeft />
<LuPanelLeftClose />
</button>
</div>
</div>
Expand Down
13 changes: 13 additions & 0 deletions frontend/lib/context/SidebarProvider/hooks/useSideBarContext.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { useContext } from "react";

import { SideBarContext } from "../sidebar-provider";

// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
export const useSideBarContext = () => {
const context = useContext(SideBarContext);
if (context === undefined) {
throw new Error("useSideBarContext must be used within a SideBarProvider");
}

return context;
};
36 changes: 36 additions & 0 deletions frontend/lib/context/SidebarProvider/sidebar-provider.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import { createContext, useEffect, useState } from "react";

import { useDevice } from "@/lib/hooks/useDevice";

type SideBarContextType = {
isOpened: boolean;
setIsOpened: React.Dispatch<React.SetStateAction<boolean>>;
};

export const SideBarContext = createContext<SideBarContextType | undefined>(
undefined
);

export const SideBarProvider = ({
children,
}: {
children: React.ReactNode;
}): JSX.Element => {
const { isMobile } = useDevice();
const [isOpened, setIsOpened] = useState(!isMobile);

useEffect(() => {
setIsOpened(!isMobile);
}, [isMobile]);

return (
<SideBarContext.Provider
value={{
isOpened,
setIsOpened,
}}
>
{children}
</SideBarContext.Provider>
);
};
1 change: 1 addition & 0 deletions frontend/public/locales/en/chat.json
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
"last30Days": "Previous 30 days",
"last7Days": "Previous 7 days",
"limit_reached": "You have reached the limit of requests, please try again later",
"menu": "Menu",
"missing_brain": "Please select a brain to chat with",
"new_discussion": "New discussion",
"new_prompt": "Create new prompt",
Expand Down
1 change: 1 addition & 0 deletions frontend/public/locales/es/chat.json
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
"last30Days": "Últimos 30 días",
"last7Days": "Últimos 7 días",
"limit_reached": "Has alcanzado el límite de peticiones, intente de nuevo más tarde",
"menu": "Menú",
"missing_brain": "No hay cerebro seleccionado",
"new_discussion": "Nueva discusión",
"new_prompt": "Crear nueva instrucción",
Expand Down
1 change: 1 addition & 0 deletions frontend/public/locales/fr/chat.json
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
"last30Days": "30 derniers jours",
"last7Days": "7 derniers jours",
"limit_reached": "Vous avez atteint la limite de requêtes, veuillez réessayer plus tard",
"menu": "Menu",
"missing_brain": "Veuillez selectionner un cerveau pour discuter",
"new_discussion": "Nouvelle discussion",
"new_prompt": "Créer un nouveau prompt",
Expand Down
1 change: 1 addition & 0 deletions frontend/public/locales/pt-br/chat.json
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
"last30Days": "Últimos 30 dias",
"last7Days": "Últimos 7 dias",
"limit_reached": "Você atingiu o limite de solicitações, por favor, tente novamente mais tarde",
"menu": "Menu",
"missing_brain": "Cérebro não encontrado",
"new_discussion": "Nova discussão",
"new_prompt": "Criar novo prompt",
Expand Down
1 change: 1 addition & 0 deletions frontend/public/locales/ru/chat.json
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
"last30Days": "Последние 30 дней",
"last7Days": "Последние 7 дней",
"limit_reached": "Вы достигли лимита запросов, пожалуйста, попробуйте позже",
"menu": "Меню",
"missing_brain": "Мозг не найден",
"new_discussion": "Новое обсуждение",
"new_prompt": "Создать новый запрос",
Expand Down
1 change: 1 addition & 0 deletions frontend/public/locales/zh-cn/chat.json
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
"last30Days": "过去30天",
"last7Days": "过去7天",
"limit_reached": "您已达到请求限制,请稍后再试",
"menu": "菜单",
"missing_brain": "请选择一个大脑进行聊天",
"new_discussion": "新讨论",
"new_prompt": "新提示",
Expand Down