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: add combobox for export options #292

Merged
merged 7 commits into from
Apr 21, 2023
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
Next Next commit
feat: add combobox for export options
  • Loading branch information
Jshen123 committed Apr 21, 2023
commit c847f306caa34ec2cb73c9416f34e478c6167288
41 changes: 29 additions & 12 deletions src/components/ChatWindow.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import { useRouter } from "next/router";
import WindowButton from "./WindowButton";
import PDFButton from "./pdf/PDFButton";
import FadeIn from "./motions/FadeIn";
import Combobox from "./Combobox";

interface ChatWindowProps extends HeaderProps {
children?: ReactNode;
Expand Down Expand Up @@ -154,6 +155,24 @@ const MacWindowHeader = (props: HeaderProps) => {
void navigator.clipboard.writeText(text);
};

const exportOptions = [
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we extract an ExportComboBox element?

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

return (
<div className="flex items-center gap-1 overflow-hidden rounded-t-3xl p-3">
<PopIn delay={0.4}>
Expand All @@ -168,20 +187,18 @@ const MacWindowHeader = (props: HeaderProps) => {
<div className="flex flex-grow font-mono text-sm font-bold text-gray-600 sm:ml-2">
{props.title}
</div>
<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"}
<Combobox
value="Export"
onChange={() => null}
options={exportOptions}
styleClass={{
container: "absolute right-1.5 md:right-2.5",
input:
"bg-[#3a3a3a] w-40 md:w-48 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]",
option: "w-full py-[1px] md:py-0.5",
}}
/>
<PDFButton messages={props.messages} />
</div>
);
};
Expand Down
62 changes: 39 additions & 23 deletions src/components/Combobox.tsx
Original file line number Diff line number Diff line change
@@ -1,22 +1,22 @@
import { useState } from "react";
import { useState, isValidElement } from "react";
import { Combobox as ComboboxPrimitive } from "@headlessui/react";
import { FaChevronDown } from "react-icons/fa";
import clsx from "clsx";

type opt = string | JSX.Element;
interface ComboboxProps {
value: string;
options: string[];
left?: boolean;
options: opt[];
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 @@ -28,37 +28,53 @@ const Combobox = ({
}
};

const filteredOptions =
const getOptionText = (opt: opt): string => {
if (isValidElement(opt)) {
return (opt.props as { name: string }).name;
}

if (typeof opt === "string") {
return opt;
}

return "";
};

const readOnlyInput = options.length <= 0 || typeof options[0] !== "string";

const filteredOptions: opt[] =
query === ""
? options
: options.filter((opt) => {
return opt.toLowerCase().includes(query.toLowerCase());
const option = getOptionText(opt);
return option.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}
readOnly={readOnlyInput}
/>
<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 ">
{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"
>
{opt}
</ComboboxPrimitive.Option>
))}
<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: opt) => {
const option = getOptionText(opt);

return (
<ComboboxPrimitive.Option
key={option}
value={option}
className={styleClass?.option}
>
{opt}
</ComboboxPrimitive.Option>
);
})}
</ComboboxPrimitive.Options>
</div>
</ComboboxPrimitive>
Expand Down
10 changes: 10 additions & 0 deletions src/components/Input.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,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 {
Expand Down
12 changes: 6 additions & 6 deletions src/components/WindowButton.tsx
Original file line number Diff line number Diff line change
@@ -1,25 +1,25 @@
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;
};

const WindowButton = ({ delay, onClick, icon, text }: WindowButtonProps) => {
const WindowButton = ({ delay, onClick, icon, name }: 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-xs hover:bg-white/10 md:text-sm"
onClick={onClick}
>
{icon}
<p className="font-mono">{text}</p>
<p className="font-mono">{name}</p>
</div>
</PopIn>
);
};

export default WindowButton;
export default memo(WindowButton);
16 changes: 11 additions & 5 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 { FaSave } from "react-icons/fa";
import { pdf } from "@react-pdf/renderer";
import React from "react";
import React, { memo } from "react";
import type { Message } from "../ChatWindow";
import MyDocument from "./MyDocument";

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={<FaSave size={12} />}
text={"Save"}
name={name}
/>
</>
);
Expand All @@ -47,4 +53,4 @@ const getContent = (messages: Message[]): string => {
.join("\n");
};

export default PDFButton;
export default memo(PDFButton);