Skip to content

Commit

Permalink
feat: add combobox for export options (reworkd#292)
Browse files Browse the repository at this point in the history
* feat: add combobox for export options

* hide AgentGPT title in chat window when screen size is small

* include save agent button

* implement menu for export buttons

* clean up

* display save Agent button separately
  • Loading branch information
Jshen123 committed Apr 21, 2023
1 parent 660feaa commit 39306a9
Show file tree
Hide file tree
Showing 6 changed files with 121 additions and 43 deletions.
53 changes: 36 additions & 17 deletions src/components/ChatWindow.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import { useRouter } from "next/router";
import WindowButton from "./WindowButton";
import PDFButton from "./pdf/PDFButton";
import FadeIn from "./motions/FadeIn";
import Menu from "./Menu";
import type { Message } from "../types/agentTypes";
import clsx from "clsx";

Expand Down Expand Up @@ -167,8 +168,26 @@ const MacWindowHeader = (props: HeaderProps) => {
void navigator.clipboard.writeText(text);
};

const exportOptions = [
<WindowButton
key="Image"
delay={0.1}
onClick={(): void => saveElementAsImage(messageListId)}
icon={<FaImage size={12} />}
name="Image"
/>,
<WindowButton
key="Copy"
delay={0.15}
onClick={(): void => copyElementText(messageListId)}
icon={<FaClipboard size={12} />}
name="Copy"
/>,
<PDFButton key="PDF" name="PDF" messages={props.messages} />,
];

return (
<div className="flex items-center gap-1 overflow-hidden rounded-t-3xl p-3">
<div className="flex items-center gap-1 overflow-visible rounded-t-3xl p-3">
<PopIn delay={0.4}>
<div className="h-3 w-3 rounded-full bg-red-500" />
</PopIn>
Expand All @@ -180,32 +199,32 @@ const MacWindowHeader = (props: HeaderProps) => {
</PopIn>
<Expand
delay={1}
className="flex flex-grow font-mono text-sm font-bold text-gray-600 sm:ml-2 "
className="invisible flex flex-grow font-mono text-sm font-bold text-gray-600 sm:ml-2 md:visible"
>
{props.title}
</Expand>
{props.onSave && (
<WindowButton
delay={0.8}
key="Agent"
delay={0}
onClick={() => props.onSave?.("db")}
icon={<FaSave size={12} />}
text={"Save"}
name={"Save"}
styleClass={{
container: `relative bg-[#3a3a3a] md:w-20 text-center font-mono rounded-lg text-gray/50 border-[2px] border-white/30 font-bold transition-all sm:py-0.5 hover:border-[#1E88E5]/40 hover:bg-[#6b6b6b] focus-visible:outline-none focus:border-[#1E88E5]`,
}}
/>
)}
<WindowButton
delay={0.7}
onClick={(): void => saveElementAsImage(messageListId)}
icon={<FaImage size={12} />}
text={"Image"}
/>

<WindowButton
delay={0.8}
onClick={(): void => copyElementText(messageListId)}
icon={<FaClipboard size={12} />}
text={"Copy"}
<Menu
name="Export"
onChange={() => null}
items={exportOptions}
styleClass={{
container: "relative",
input: `bg-[#3a3a3a] w-28 animation-duration text-left px-4 text-sm p-1 font-mono rounded-lg text-gray/50 border-[2px] border-white/30 font-bold transition-all sm:py-0.5 hover:border-[#1E88E5]/40 hover:bg-[#6b6b6b] focus-visible:outline-none focus:border-[#1E88E5]`,
option: "w-full py-[1px] md:py-0.5",
}}
/>
<PDFButton messages={props.messages} />
</div>
);
};
Expand Down
23 changes: 9 additions & 14 deletions src/components/Combobox.tsx
Original file line number Diff line number Diff line change
@@ -1,22 +1,21 @@
import { useState } from "react";
import { Combobox as ComboboxPrimitive } from "@headlessui/react";
import { FaChevronDown } from "react-icons/fa";
import clsx from "clsx";

interface ComboboxProps {
value: string;
options: string[];
left?: boolean;
disabled?: boolean;
onChange: (value: string) => void;
styleClass?: { [key: string]: string };
}

const Combobox = ({
options,
value,
left = true,
disabled,
onChange,
styleClass,
}: ComboboxProps) => {
const [query, setQuery] = useState("");
const handleInputChange = (event: React.ChangeEvent<HTMLInputElement>) => {
Expand All @@ -31,30 +30,26 @@ const Combobox = ({
const filteredOptions =
query === ""
? options
: options.filter((opt) => {
return opt.toLowerCase().includes(query.toLowerCase());
});
: options.filter((opt) =>
opt.toLowerCase().includes(query.toLowerCase())
);

return (
<ComboboxPrimitive value={value} onChange={onChange} disabled={disabled}>
<div className="relative w-full">
<div className={styleClass?.container}>
<ComboboxPrimitive.Input
onChange={handleInputChange}
className={clsx(
"border:black delay-50 sm: flex w-full items-center justify-between rounded-xl border-[2px] border-white/10 bg-transparent px-2 py-2 text-sm tracking-wider outline-0 transition-all hover:border-[#1E88E5]/40 focus:border-[#1E88E5] sm:py-3 md:text-lg",
disabled && " cursor-not-allowed hover:border-white/10",
left && "md:rounded-l-none"
)}
className={styleClass?.input}
/>
<ComboboxPrimitive.Button className="absolute inset-y-0 right-0 flex items-center pr-4">
<FaChevronDown className="h-5 w-5 text-gray-400" aria-hidden="true" />
</ComboboxPrimitive.Button>
<ComboboxPrimitive.Options className="absolute right-0 top-full z-20 mt-1 max-h-48 w-full overflow-auto rounded-xl border-[2px] border-white/10 bg-[#3a3a3a] tracking-wider shadow-xl outline-0 transition-all ">
<ComboboxPrimitive.Options className="absolute right-0 top-full z-20 mt-1 max-h-48 w-full overflow-hidden rounded-xl border-[2px] border-white/10 bg-[#3a3a3a] tracking-wider shadow-xl outline-0 transition-all">
{filteredOptions.map((opt) => (
<ComboboxPrimitive.Option
key={opt}
value={opt}
className="cursor-pointer px-2 py-2 font-mono text-sm text-white/75 hover:bg-blue-500 sm:py-3 md:text-lg"
className={styleClass?.option}
>
{opt}
</ComboboxPrimitive.Option>
Expand Down
10 changes: 10 additions & 0 deletions src/components/Input.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,16 @@ const Input = (props: InputProps) => {
options={options}
disabled={disabled}
onChange={setValue}
styleClass={{
container: "relative w-full",
options:
"absolute right-0 top-full z-20 mt-1 max-h-48 w-full overflow-auto rounded-xl border-[2px] border-white/10 bg-[#3a3a3a] tracking-wider shadow-xl outline-0 transition-all",
input: `border:black delay-50 sm: flex w-full items-center justify-between rounded-xl border-[2px] border-white/10 bg-transparent px-2 py-2 text-sm tracking-wider outline-0 transition-all hover:border-[#1E88E5]/40 focus:border-[#1E88E5] sm:py-3 md:text-lg ${
disabled ? "cursor-not-allowed hover:border-white/10" : ""
} ${left ? "md:rounded-l-none" : ""}`,
option:
"cursor-pointer px-2 py-2 font-mono text-sm text-white/75 hover:bg-blue-500 sm:py-3 md:text-lg",
}}
/>
);
} else if (isTypeTextArea()) {
Expand Down
39 changes: 39 additions & 0 deletions src/components/Menu.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import { Fragment, memo } from "react";
import { Menu as MenuPrimitive } from "@headlessui/react";
import { FaChevronDown } from "react-icons/fa";

interface MenuProps {
name: string;
items: JSX.Element[];
disabled?: boolean;
onChange: (value: string) => void;
styleClass?: { [key: string]: string };
}

function Menu({ name, items, disabled, onChange, styleClass }: MenuProps) {
return (
<MenuPrimitive>
<div className={styleClass?.container}>
<MenuPrimitive.Button className={styleClass?.input}>
<span>{name}</span>
<FaChevronDown
className="absolute right-1.5 inline-block h-5 w-5 text-gray-400"
aria-hidden="true"
/>
</MenuPrimitive.Button>
<MenuPrimitive.Items className="absolute right-0 top-full z-20 mt-1 max-h-48 w-full overflow-hidden rounded-xl border-[2px] border-white/10 bg-[#3a3a3a] tracking-wider shadow-xl outline-0 transition-all">
{items.map((item) => {
const itemName = (item.props as { name: string }).name;
return (
<MenuPrimitive.Item key={itemName} as={Fragment}>
<div className={styleClass?.option}>{item}</div>
</MenuPrimitive.Item>
);
})}
</MenuPrimitive.Items>
</div>
</MenuPrimitive>
);
}

export default memo(Menu);
21 changes: 15 additions & 6 deletions src/components/WindowButton.tsx
Original file line number Diff line number Diff line change
@@ -1,25 +1,34 @@
import PopIn from "./motions/popin";
import React from "react";
import React, { memo } from "react";

type WindowButtonProps = {
delay: number;
onClick?: () => void;
icon: React.ReactNode;
text: string;
name: string;
styleClass?: { [key: string]: string };
};

const WindowButton = ({ delay, onClick, icon, text }: WindowButtonProps) => {
const WindowButton = ({
delay,
onClick,
icon,
name,
styleClass,
}: WindowButtonProps) => {
return (
<PopIn delay={delay}>
<div
className="mr-1 flex cursor-pointer items-center gap-2 rounded-full border-2 border-white/30 p-1 px-2 text-xs hover:bg-white/10"
className={`flex cursor-pointer items-center gap-2 p-1 px-2 text-sm hover:bg-white/10 ${
styleClass?.container || ""
}`}
onClick={onClick}
>
{icon}
<p className="font-mono">{text}</p>
<p className="font-mono">{name}</p>
</div>
</PopIn>
);
};

export default WindowButton;
export default memo(WindowButton);
18 changes: 12 additions & 6 deletions src/components/pdf/PDFButton.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,17 @@
import WindowButton from "../WindowButton";
import { FaFilePdf, FaRegFilePdf, FaSave } from "react-icons/fa";
import { FaFilePdf } from "react-icons/fa";
import { pdf } from "@react-pdf/renderer";
import React from "react";
import React, { memo } from "react";
import MyDocument from "./MyDocument";
import type { Message } from "../../types/agentTypes";

const PDFButton = ({ messages }: { messages: Message[] }) => {
const PDFButton = ({
messages,
name,
}: {
messages: Message[];
name: string;
}) => {
const content = getContent(messages);

const downloadPDF = async () => {
Expand All @@ -21,12 +27,12 @@ const PDFButton = ({ messages }: { messages: Message[] }) => {
return (
<>
<WindowButton
delay={0.8}
delay={0.2}
onClick={() => {
downloadPDF().catch(console.error);
}}
icon={<FaFilePdf size={12} />}
text={"PDF"}
name={name}
/>
</>
);
Expand All @@ -47,4 +53,4 @@ const getContent = (messages: Message[]): string => {
.join("\n");
};

export default PDFButton;
export default memo(PDFButton);

0 comments on commit 39306a9

Please sign in to comment.