mirror of
https://github.com/StanGirard/quivr.git
synced 2024-11-23 12:26:03 +03:00
feat: add new actions modal (#1870)
Issue: https://github.com/StanGirard/quivr/issues/1861 - Update Quivr font - Add Actions modal - Update Popover component - Move Chat config to Actions modal Demo: https://github.com/StanGirard/quivr/assets/63923024/df3ac138-6950-46fe-8e40-6276005c7ef1
This commit is contained in:
parent
f28e009d98
commit
a30042f0fc
@ -0,0 +1,42 @@
|
||||
import { PopoverAnchor } from "@radix-ui/react-popover";
|
||||
import { useState } from "react";
|
||||
import { LuPlusCircle, LuXCircle } from "react-icons/lu";
|
||||
|
||||
import Button from "@/lib/components/ui/Button";
|
||||
import {
|
||||
Popover,
|
||||
PopoverContent,
|
||||
PopoverTrigger,
|
||||
} from "@/lib/components/ui/Popover";
|
||||
|
||||
import { ConfigModal } from "./components/ConfigModal";
|
||||
|
||||
export const ActionsModal = (): JSX.Element => {
|
||||
const [isActionsModalOpened, setIsActionsModalOpened] = useState(false);
|
||||
|
||||
const Icon = isActionsModalOpened ? LuXCircle : LuPlusCircle;
|
||||
|
||||
return (
|
||||
<div className="flex items-center">
|
||||
<Popover
|
||||
open={isActionsModalOpened}
|
||||
onOpenChange={(isOpened) => setIsActionsModalOpened(isOpened)}
|
||||
>
|
||||
<PopoverTrigger>
|
||||
<PopoverAnchor asChild>
|
||||
<Button variant="tertiary" type="button" className="p-0">
|
||||
<Icon className="text-accent font-bold" size={30} />
|
||||
</Button>
|
||||
</PopoverAnchor>
|
||||
</PopoverTrigger>
|
||||
<PopoverContent
|
||||
align="end"
|
||||
sideOffset={15}
|
||||
className="min-h-[200px] w-[200px]"
|
||||
>
|
||||
<ConfigModal />
|
||||
</PopoverContent>
|
||||
</Popover>
|
||||
</div>
|
||||
);
|
||||
};
|
@ -0,0 +1,42 @@
|
||||
import { forwardRef } from "react";
|
||||
|
||||
import CoreButton, {
|
||||
ButtonProps as CoreButtonProps,
|
||||
} from "@/lib/components/ui/Button";
|
||||
import { cn } from "@/lib/utils";
|
||||
|
||||
type ButtonProps = CoreButtonProps & {
|
||||
onClick?: () => void;
|
||||
className?: string;
|
||||
label?: string;
|
||||
startIcon?: JSX.Element;
|
||||
endIcon?: JSX.Element;
|
||||
};
|
||||
|
||||
export const Button = forwardRef(
|
||||
(
|
||||
{ onClick, className, label, startIcon, endIcon, ...props }: ButtonProps,
|
||||
forwardedRef
|
||||
): JSX.Element => {
|
||||
return (
|
||||
<CoreButton
|
||||
className={cn("p-2 sm:px-3 text-primary focus:ring-0 ", className)}
|
||||
variant={"tertiary"}
|
||||
data-testid="config-button"
|
||||
ref={forwardedRef}
|
||||
onClick={onClick}
|
||||
{...props}
|
||||
>
|
||||
<div className="flex flex-row justify-between w-full items-center">
|
||||
<div className="flex flex-row gap-2 items-center">
|
||||
{startIcon}
|
||||
<span className="hidden sm:block">{label}</span>
|
||||
</div>
|
||||
{endIcon}
|
||||
</div>
|
||||
</CoreButton>
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
Button.displayName = CoreButton.displayName;
|
@ -1,12 +1,12 @@
|
||||
/* eslint-disable max-lines */
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { MdCheck, MdSettings } from "react-icons/md";
|
||||
import { LuChevronRight, LuSettings } from "react-icons/lu";
|
||||
import { MdCheck } from "react-icons/md";
|
||||
|
||||
import Button from "@/lib/components/ui/Button";
|
||||
import { Modal } from "@/lib/components/ui/Modal";
|
||||
import { defineMaxTokens } from "@/lib/helpers/defineMaxTokens";
|
||||
|
||||
import { useConfigModal } from "./hooks/useConfigModal";
|
||||
import { Button } from "../Button";
|
||||
|
||||
export const ConfigModal = (): JSX.Element => {
|
||||
const {
|
||||
@ -24,12 +24,11 @@ export const ConfigModal = (): JSX.Element => {
|
||||
<Modal
|
||||
Trigger={
|
||||
<Button
|
||||
className="p-2 sm:px-3"
|
||||
variant={"tertiary"}
|
||||
data-testid="config-button"
|
||||
>
|
||||
<MdSettings className="text-lg sm:text-xl lg:text-2xl" />
|
||||
</Button>
|
||||
label={"Parametres"}
|
||||
startIcon={<LuSettings size={18} />}
|
||||
endIcon={<LuChevronRight size={18} />}
|
||||
className="w-full"
|
||||
/>
|
||||
}
|
||||
title="Chat configuration"
|
||||
desc="Adjust your chat settings"
|
||||
@ -68,16 +67,16 @@ export const ConfigModal = (): JSX.Element => {
|
||||
</fieldset>
|
||||
|
||||
<Button
|
||||
className="mt-12 self-end"
|
||||
className="mt-12 self-end text-white"
|
||||
type="button"
|
||||
onClick={() => {
|
||||
handleSubmit();
|
||||
setIsConfigModalOpen(false);
|
||||
}}
|
||||
>
|
||||
Save
|
||||
<MdCheck className="text-xl" />
|
||||
</Button>
|
||||
variant={"primary"}
|
||||
label="Save"
|
||||
endIcon={<MdCheck className="text-xl" />}
|
||||
/>
|
||||
</form>
|
||||
</Modal>
|
||||
);
|
@ -1,2 +1 @@
|
||||
export * from "./ConfigModal";
|
||||
export * from "./OnboardingQuestions";
|
||||
|
@ -7,8 +7,8 @@ import { useKnowledgeToFeedContext } from "@/lib/context/KnowledgeToFeedProvider
|
||||
import { getBrainIconFromBrainType } from "@/lib/helpers/getBrainIconFromBrainType";
|
||||
|
||||
import { OnboardingQuestions } from "./components";
|
||||
import { ActionsModal } from "./components/ActionsModal/ActionsModal";
|
||||
import { ChatEditor } from "./components/ChatEditor/ChatEditor";
|
||||
import { ConfigModal } from "./components/ConfigModal";
|
||||
import { useChatInput } from "./hooks/useChatInput";
|
||||
|
||||
type ChatInputProps = {
|
||||
@ -58,9 +58,9 @@ export const ChatInput = ({
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="flex flex-row items-end">
|
||||
<div className="flex flex-row items-center gap-4">
|
||||
<Button
|
||||
className="px-3 py-2 sm:px-4 sm:py-2"
|
||||
className="px-3 py-2 sm:px-4 sm:py-2 bg-primary border-0"
|
||||
type="submit"
|
||||
isLoading={generatingAnswer}
|
||||
data-testid="submit-button"
|
||||
@ -69,9 +69,7 @@ export const ChatInput = ({
|
||||
? t("thinking", { ns: "chat" })
|
||||
: t("chat", { ns: "chat" })}
|
||||
</Button>
|
||||
<div className="hidden md:flex items-center">
|
||||
<ConfigModal />
|
||||
</div>
|
||||
<ActionsModal />
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
@ -1,6 +1,6 @@
|
||||
import { createServerComponentSupabaseClient } from "@supabase/auth-helpers-nextjs";
|
||||
import { Analytics as VercelAnalytics } from "@vercel/analytics/react";
|
||||
import { Inter } from "next/font/google";
|
||||
import { Outfit } from "next/font/google";
|
||||
import { cookies, headers } from "next/headers";
|
||||
|
||||
import { ToastProvider } from "@/lib/components/ui/Toast";
|
||||
@ -10,7 +10,7 @@ import { SupabaseProvider } from "@/lib/context/SupabaseProvider";
|
||||
import { App } from "./App";
|
||||
import "./globals.css";
|
||||
|
||||
const inter = Inter({ subsets: ["latin"] });
|
||||
const inter = Outfit({ subsets: ["latin"], weight: "400" });
|
||||
|
||||
export const metadata = {
|
||||
title: "Quivr - Get a Second Brain with Generative AI",
|
||||
|
@ -1,59 +0,0 @@
|
||||
import { FaLanguage } from "react-icons/fa";
|
||||
import { MdCheck } from "react-icons/md";
|
||||
|
||||
import Popover from "@/lib/components/ui/Popover";
|
||||
|
||||
import { useLanguageHook } from "./hooks/useLanguageHook";
|
||||
|
||||
export const LanguageDropDown = (): JSX.Element => {
|
||||
const { allLanguages, currentLanguage, change } = useLanguageHook();
|
||||
|
||||
return (
|
||||
<>
|
||||
{/* Add the brain icon and dropdown */}
|
||||
<div className="focus:outline-none text-3xl">
|
||||
<Popover
|
||||
Trigger={
|
||||
<button
|
||||
type="button"
|
||||
className="flex items-center focus:outline-none"
|
||||
>
|
||||
<FaLanguage className="w-6 h-6" />
|
||||
</button>
|
||||
}
|
||||
CloseTrigger={false}
|
||||
>
|
||||
<div>
|
||||
<div className="overflow-auto scrollbar flex flex-col h-48 mt-5">
|
||||
{Object.keys(allLanguages).map((lang) => {
|
||||
return (
|
||||
<div key={lang} className="relative flex group items-center">
|
||||
<button
|
||||
type="button"
|
||||
className={`flex flex-1 items-center gap-2 w-full text-left px-4 py-2 text-sm leading-5 text-gray-900 dark:text-gray-300 group-hover:bg-gray-100 dark:group-hover:bg-gray-700 group-focus:bg-gray-100 dark:group-focus:bg-gray-700 group-focus:outline-none transition-colors`}
|
||||
onClick={() => {
|
||||
change(lang);
|
||||
}}
|
||||
>
|
||||
<span>
|
||||
<MdCheck
|
||||
style={{
|
||||
opacity: currentLanguage === lang ? 1 : 0,
|
||||
}}
|
||||
className="text-xl transition-opacity"
|
||||
width={32}
|
||||
height={32}
|
||||
/>
|
||||
</span>
|
||||
<span className="flex-1">{allLanguages[lang].label}</span>
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
</Popover>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
};
|
@ -1 +0,0 @@
|
||||
export * from "./LanguageDropDown";
|
@ -9,7 +9,7 @@ import { redirectToLogin } from "@/lib/router/redirectToLogin";
|
||||
|
||||
import { StripePricingOrManageButton, UserStatistics } from "./components";
|
||||
import { ApiKeyConfig } from "./components/ApiKeyConfig";
|
||||
import LanguageSelect from "./components/LanguageDropDown/LanguageSelect";
|
||||
import LanguageSelect from "./components/LanguageSelect/LanguageSelect";
|
||||
import { LogoutModal } from "./components/LogoutCard/LogoutModal";
|
||||
import ThemeSelect from "./components/ThemeSelect/ThemeSelect";
|
||||
|
||||
|
@ -70,6 +70,8 @@ export const UserToInvite = ({
|
||||
onChange={setSelectedRole}
|
||||
value={selectedRole}
|
||||
options={translatedOptions}
|
||||
popoverSide="bottom"
|
||||
popoverClassName="w-36"
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
|
@ -68,7 +68,6 @@ const Button = forwardRef(
|
||||
{children} {isLoading && <FaSpinner className="animate-spin" />}
|
||||
</>
|
||||
);
|
||||
const buttonElement = <button {...buttonProps}>buttonChildren</button>;
|
||||
|
||||
if (tooltip !== undefined) {
|
||||
return (
|
||||
|
@ -1,71 +1,29 @@
|
||||
"use client";
|
||||
import * as PopoverPrimitive from "@radix-ui/react-popover";
|
||||
import { AnimatePresence, motion } from "framer-motion";
|
||||
import { ReactNode, useState } from "react";
|
||||
import * as React from "react";
|
||||
|
||||
import Button from "./Button";
|
||||
import { cn } from "@/lib/utils";
|
||||
|
||||
interface PopoverProps {
|
||||
children?: ReactNode;
|
||||
Trigger: ReactNode;
|
||||
ActionTrigger?: ReactNode;
|
||||
CloseTrigger?: ReactNode;
|
||||
}
|
||||
const Popover = PopoverPrimitive.Root;
|
||||
|
||||
const Popover = ({
|
||||
children,
|
||||
Trigger,
|
||||
ActionTrigger,
|
||||
CloseTrigger,
|
||||
}: PopoverProps): JSX.Element => {
|
||||
const [open, setOpen] = useState(false);
|
||||
const PopoverTrigger = PopoverPrimitive.Trigger;
|
||||
|
||||
return (
|
||||
<PopoverPrimitive.Root open={open} onOpenChange={setOpen}>
|
||||
<PopoverPrimitive.Trigger asChild>{Trigger}</PopoverPrimitive.Trigger>
|
||||
<AnimatePresence>
|
||||
{open && (
|
||||
<PopoverPrimitive.Portal forceMount>
|
||||
<PopoverPrimitive.Content forceMount asChild sideOffset={5}>
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: -32 }}
|
||||
animate={{
|
||||
opacity: 1,
|
||||
y: 0,
|
||||
}}
|
||||
exit={{ opacity: 0, y: -32 }}
|
||||
transition={{ duration: 0.2, ease: "easeInOut" }}
|
||||
className="relative flex flex-col p-4 bg-white dark:bg-gray-800 border border-gray-300 dark:border-gray-700 rounded-lg shadow-lg z-50 md:z-40"
|
||||
>
|
||||
<div className="flex-1">{children}</div>
|
||||
<div className="mt-4 self-end flex gap-4">
|
||||
{ActionTrigger !== undefined && (
|
||||
<PopoverPrimitive.Close asChild>
|
||||
{ActionTrigger}
|
||||
</PopoverPrimitive.Close>
|
||||
)}
|
||||
<PopoverPrimitive.Close asChild>
|
||||
{CloseTrigger === undefined ? (
|
||||
<Button
|
||||
variant={"secondary"}
|
||||
className="px-3 py-2"
|
||||
aria-label="Close"
|
||||
>
|
||||
Close
|
||||
</Button>
|
||||
) : (
|
||||
CloseTrigger
|
||||
)}
|
||||
</PopoverPrimitive.Close>
|
||||
</div>
|
||||
<PopoverPrimitive.Arrow className="fill-white stroke-gray-300 stroke-2" />
|
||||
</motion.div>
|
||||
</PopoverPrimitive.Content>
|
||||
</PopoverPrimitive.Portal>
|
||||
)}
|
||||
</AnimatePresence>
|
||||
</PopoverPrimitive.Root>
|
||||
);
|
||||
};
|
||||
const PopoverContent = React.forwardRef<
|
||||
React.ElementRef<typeof PopoverPrimitive.Content>,
|
||||
React.ComponentPropsWithoutRef<typeof PopoverPrimitive.Content>
|
||||
>(({ className, align = "center", sideOffset = 4, ...props }, ref) => (
|
||||
<PopoverPrimitive.Portal>
|
||||
<PopoverPrimitive.Content
|
||||
ref={ref}
|
||||
align={align}
|
||||
sideOffset={sideOffset}
|
||||
className={cn(
|
||||
"z-50 w-72 rounded-md border bg-white p-4 text-popover-foreground shadow-md outline-none data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
</PopoverPrimitive.Portal>
|
||||
));
|
||||
PopoverContent.displayName = PopoverPrimitive.Content.displayName;
|
||||
|
||||
export default Popover;
|
||||
export { Popover, PopoverContent, PopoverTrigger };
|
||||
|
@ -1,7 +1,13 @@
|
||||
/*eslint complexity: ["error", 10]*/
|
||||
|
||||
/* eslint-disable max-lines */
|
||||
import { BsCheckCircleFill } from "react-icons/bs";
|
||||
|
||||
import Popover from "@/lib/components/ui/Popover";
|
||||
import {
|
||||
Popover,
|
||||
PopoverContent,
|
||||
PopoverTrigger,
|
||||
} from "@/lib/components/ui/Popover";
|
||||
import { cn } from "@/lib/utils";
|
||||
|
||||
export type SelectOptionProps<T> = {
|
||||
@ -17,6 +23,8 @@ type SelectProps<T> = {
|
||||
readOnly?: boolean;
|
||||
className?: string;
|
||||
emptyLabel?: string;
|
||||
popoverClassName?: string;
|
||||
popoverSide?: "top" | "bottom" | "left" | "right" | undefined;
|
||||
};
|
||||
|
||||
const selectedStyle = "rounded-lg bg-black text-white";
|
||||
@ -29,6 +37,8 @@ export const Select = <T extends string | number>({
|
||||
readOnly = false,
|
||||
className,
|
||||
emptyLabel,
|
||||
popoverClassName,
|
||||
popoverSide,
|
||||
}: SelectProps<T>): JSX.Element => {
|
||||
const selectedValueLabel = options.find(
|
||||
(option) => option.value === value
|
||||
@ -74,8 +84,8 @@ export const Select = <T extends string | number>({
|
||||
</label>
|
||||
)}
|
||||
<div className="relative">
|
||||
<Popover
|
||||
Trigger={
|
||||
<Popover>
|
||||
<PopoverTrigger>
|
||||
<button
|
||||
type="button"
|
||||
className="relative w-full cursor-default rounded-md bg-white py-1.5 pl-3 pr-10 text-left text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300 focus:outline-none focus:ring-2 focus:ring-indigo-500 sm:text-sm sm:leading-6"
|
||||
@ -101,30 +111,33 @@ export const Select = <T extends string | number>({
|
||||
</svg>
|
||||
</span>
|
||||
</button>
|
||||
}
|
||||
CloseTrigger={<div />}
|
||||
>
|
||||
<ul role="listbox">
|
||||
{options.map((option) => (
|
||||
<li
|
||||
className="text-gray-900 relative cursor-pointer select-none py-2"
|
||||
id="listbox-option-0"
|
||||
key={option.value}
|
||||
onClick={() => onChange(option.value)}
|
||||
>
|
||||
<div
|
||||
className={`flex items-center px-3 py-2 ${
|
||||
value === option.value && selectedStyle
|
||||
}`}
|
||||
</PopoverTrigger>
|
||||
<PopoverContent
|
||||
className={cn("max-h-[200px] overflow-scroll", popoverClassName)}
|
||||
side={popoverSide ?? "top"}
|
||||
>
|
||||
<ul role="listbox">
|
||||
{options.map((option) => (
|
||||
<li
|
||||
className="text-gray-900 relative cursor-pointer select-none py-0"
|
||||
id="listbox-option-0"
|
||||
key={option.value}
|
||||
onClick={() => onChange(option.value)}
|
||||
>
|
||||
<span className="font-bold block truncate mr-2">
|
||||
{option.label}
|
||||
</span>
|
||||
{value === option.value && <BsCheckCircleFill />}
|
||||
</div>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
<div
|
||||
className={`flex items-center px-3 py-2 ${
|
||||
value === option.value && selectedStyle
|
||||
}`}
|
||||
>
|
||||
<span className="font-bold block truncate mr-2">
|
||||
{option.label}
|
||||
</span>
|
||||
{value === option.value && <BsCheckCircleFill />}
|
||||
</div>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</PopoverContent>
|
||||
</Popover>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -16,7 +16,8 @@ module.exports = {
|
||||
},
|
||||
colors: {
|
||||
black: "#11243E",
|
||||
primary: "#7A27FD",
|
||||
primary: "#6142D4",
|
||||
accent: "#13ABBA",
|
||||
"chat-bg-gray": "#D9D9D9",
|
||||
"msg-gray": "#9B9B9B",
|
||||
"msg-header-gray": "#8F8F8F",
|
||||
|
Loading…
Reference in New Issue
Block a user