Skip to content

Commit

Permalink
feature: feedback button (#423)
Browse files Browse the repository at this point in the history
abvthecity authored Feb 2, 2024
1 parent 9bf4502 commit 5f0b5ad
Showing 4 changed files with 195 additions and 3 deletions.
Original file line number Diff line number Diff line change
@@ -10,9 +10,8 @@ export const BottomNavigationButtons: React.FC = () => {
}

return (
<div className="flex flex-col">
<div className="mb-6 mt-10 h-px bg-[#A7A7B0]/20"></div>
<div className="flex justify-between">
<div className="border-border-default-light dark:border-border-default-dark mt-12 flex flex-col border-t">
<div className="flex justify-between py-10">
{leftNeighbor != null ? (
<BottomNavigationButton docsNode={leftNeighbor} direction="previous" />
) : (
107 changes: 107 additions & 0 deletions packages/ui/app/src/components/FernButton.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
import classNames from "classnames";
import { ButtonHTMLAttributes, DetailedHTMLProps, FC, ReactNode } from "react";
import { RemoteFontAwesomeIcon } from "../commons/FontAwesomeIcon";

interface FernButtonProps extends DetailedHTMLProps<ButtonHTMLAttributes<HTMLButtonElement>, HTMLButtonElement> {
icon?: string | ReactNode;
rightIcon?: string | ReactNode;
minimal?: boolean;
intent?: "none" | "primary" | "success" | "danger";
small?: boolean;
mono?: boolean;
active?: boolean;
full?: boolean;
disabled?: boolean;
}
export const FernButton: FC<FernButtonProps> = ({
icon: leftIcon,
rightIcon,
className,
children,
minimal = false,
intent = "none",
small = false,
mono = false,
active = false,
full = false,
disabled = false,
...props
}) => {
function renderIcon(icon: string | ReactNode | undefined) {
if (typeof icon === "string") {
return (
<RemoteFontAwesomeIcon
icon={icon}
className={classNames("h-4 w-4", {
"bg-accent-primary dark:bg-accent-primary-dark": intent === "primary",
"bg-text-primary-light/60 dark:bg-text-primary-dark/60": intent === "none",
"bg-intent-success-light dark:bg-intent-success-dark": intent === "success",
"bg-intent-danger-light dark:bg-intent-danger-dark": intent === "danger",
})}
/>
);
} else {
return icon;
}
}

return (
<button
{...props}
className={classNames(className, "fern-button transition-shadow text-center align-middle", {
"rounded-md": small && !className?.includes("rounded"),
"rounded-lg": !small && !className?.includes("rounded"),
"px-2 py-1 text-xs h-6": small,
"px-3.5 py-1.5 text-sm h-9": !small,
"border ring-0 hover:ring-2": !minimal,
"border-border-primary dark:border-border-primary-dark ring-border-primary/10 dark:ring-border-primary-dark/10":
!minimal && intent === "primary",
"ring-text-primary dark:ring-text-primary-dark": !minimal && intent === "none",
"ring-intent-success-light dark:ring-intent-success-dark": !minimal && intent === "success",
"ring-intent-danger-light dark:ring-intent-danger-dark": !minimal && intent === "danger",
"hover:bg-tag-primary text-accent-primary dark:text-accent-primary-dark": intent === "primary",
"hover:bg-tag-primary bg-transparent dark:bg-transparent-dark text-text-primary-light/60 dark:text-text-primary-dark/60":
intent === "none",
"hover:bg-tag-success text-intent-success-light dark:text-intent-success-dark": intent === "success",
"hover:bg-tag-danger text-intent-danger-light dark:text-intent-danger-dark": intent === "danger",
"bg-tag-primary": intent === "primary" && active,
"bg-tag-success": intent === "success" && active,
"bg-tag-danger": intent === "danger" && active,
"w-full": full,
})}
onClick={
props.onClick != null
? (e) => {
if (disabled) {
e.preventDefault();
e.stopPropagation();
} else {
props.onClick?.(e);
}
}
: undefined
}
>
<span
className={classNames("inline-flex items-center", {
"gap-1": small,
"h-[14px]": small && !minimal,
"h-4": small && minimal,
"gap-1.5": !small,
"h-[22px]": !small && !minimal,
"h-6": !small && minimal,
})}
>
{renderIcon(leftIcon)}
<span
className={classNames({
"font-mono tracking-tight": mono,
})}
>
{children}
</span>
{renderIcon(rightIcon)}
</span>
</button>
);
};
3 changes: 3 additions & 0 deletions packages/ui/app/src/custom-docs-page/CustomDocsPage.tsx
Original file line number Diff line number Diff line change
@@ -5,6 +5,7 @@ import { ReactElement } from "react";
import { renderToString } from "react-dom/server";
import { BottomNavigationButtons } from "../bottom-navigation-buttons/BottomNavigationButtons";
import { MdxContent } from "../mdx/MdxContent";
import { Feedback } from "./Feedback";
import { TableOfContents } from "./TableOfContents";
import { TableOfContentsContextProvider } from "./TableOfContentsContext";

@@ -43,6 +44,8 @@ export const CustomDocsPage: React.FC<CustomDocsPage.Props> = ({ resolvedPath })
<article className="prose dark:prose-invert mx-auto w-full max-w-[70ch] lg:ml-0 xl:mx-auto">
<CustomDocsPageHeader resolvedPath={resolvedPath} />
{mdxContent}

<Feedback />
<BottomNavigationButtons />
<div className="h-20" />
</article>
83 changes: 83 additions & 0 deletions packages/ui/app/src/custom-docs-page/Feedback.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
import { FC, useRef, useState } from "react";
import { capturePosthogEvent } from "../analytics/posthog";
import { FernButton } from "../components/FernButton";
import { FernCollapse } from "../components/FernCollapse";

interface FeedbackProps {}

export const Feedback: FC<FeedbackProps> = () => {
const [sent, setSent] = useState(false);
const [feedback, setFeedback] = useState<"yes" | "no" | null>(null);
const [showFeedbackInput, setShowFeedbackInput] = useState(false);
const textareaRef = useRef<HTMLTextAreaElement>(null);
const handleYes = () => {
setFeedback("yes");
setShowFeedbackInput(true);
textareaRef.current?.focus();
capturePosthogEvent("feedback_voted", {
satisfied: true,
});
};
const handleNo = () => {
setFeedback("no");
setShowFeedbackInput(true);
textareaRef.current?.focus();
capturePosthogEvent("feedback_voted", {
satisfied: false,
});
};
const handleSubmitFeedback = () => {
capturePosthogEvent("feedback_submitted", {
satisfied: feedback === "yes" ? true : false,
message: textareaRef.current?.value,
});
setSent(true);
};
return (
<div className="border-border-default-light dark:border-border-default-dark group my-12 w-fit rounded-lg border">
{!sent ? (
<div className="flex items-center gap-4 px-4 py-2">
<span className="t-muted text-sm">Did this page help you?</span>
<div className="flex items-center">
<FernButton
icon="regular thumbs-up"
minimal
intent={feedback === "yes" ? "success" : "none"}
onClick={handleYes}
active={feedback === "yes"}
>
Yes
</FernButton>
<FernButton
icon="regular thumbs-down"
minimal={true}
intent={feedback === "no" ? "danger" : "none"}
onClick={handleNo}
active={feedback === "no"}
>
No
</FernButton>
</div>
</div>
) : (
<div className="px-4 py-2">
<div className="t-muted text-sm">Thank you for your feedback!</div>
</div>
)}
{!sent && (
<FernCollapse isOpen={showFeedbackInput}>
<div className="px-4 pb-4">
<textarea
ref={textareaRef}
autoFocus={true}
className="border-border-default-light dark:border-border-default-dark focus-visible:ring-tag-primary focus-visible:dark:ring-tag-primary-dark focus-visible:border-accent-primary focus-visible:dark:border-accent-primary-dark w-full rounded-md border bg-white p-2 text-sm focus:outline-none focus-visible:ring-2 dark:bg-white/10"
/>
<FernButton full={true} intent="primary" className="rounded-md" onClick={handleSubmitFeedback}>
Send feedback
</FernButton>
</div>
</FernCollapse>
)}
</div>
);
};

0 comments on commit 5f0b5ad

Please sign in to comment.