{doc.toc && !!doc.toc.length &&
}
@@ -84,6 +88,7 @@ export function RenderSideBar({ doc }) {
{doc.sidebarHTML && (
<>
(
+ "#sidebar-filter-input"
+ );
+ return input?.value;
+}
diff --git a/client/src/ui/atoms/icon/index.scss b/client/src/ui/atoms/icon/index.scss
index 08ea074a9dec..a99aeebfd6c0 100644
--- a/client/src/ui/atoms/icon/index.scss
+++ b/client/src/ui/atoms/icon/index.scss
@@ -1,7 +1,7 @@
$icons: "add-filled", "add", "altname", "bell-filled", "bell", "bookmark-filled",
"bookmark", "cancel", "checkmark", "chevron", "chrome", "critical", "deno",
"deprecated", "desktop", "disabled", "edge", "edit", "ellipses",
- "experimental", "external", "eye-filled", "eye", "footnote",
+ "experimental", "external", "eye-filled", "eye", "filter", "footnote",
"github-mark-small", "ie", "information", "language", "menu-filled", "menu",
"mobile", "more", "theme-dark", "next", "no", "nodejs", "nonstandard",
"note-info", "note-warning", "note-deprecated", "opera", "padlock", "partial",
diff --git a/client/src/ui/atoms/thumbs/index.scss b/client/src/ui/atoms/thumbs/index.scss
new file mode 100644
index 000000000000..2b54cb149105
--- /dev/null
+++ b/client/src/ui/atoms/thumbs/index.scss
@@ -0,0 +1,54 @@
+.glean-thumbs {
+ align-items: center;
+ display: flex;
+ flex-direction: row;
+ gap: 0.5rem;
+
+ .confirmation {
+ animation-duration: 2.5s;
+ animation-fill-mode: forwards;
+ animation-name: colorHighlight;
+ color: var(--category-color);
+
+ @keyframes colorHighlight {
+ 0% {
+ opacity: 0;
+ }
+
+ 25% {
+ color: var(--category-color);
+ opacity: 1;
+ }
+
+ 75% {
+ color: var(--category-color);
+ }
+
+ 100% {
+ color: var(--text-inactive);
+ }
+ }
+ }
+}
+
+.thumbs {
+ &:hover {
+ --button-bg-hover: unset;
+ }
+
+ &:active {
+ --button-bg-active: unset;
+ }
+
+ &:focus,
+ &:hover,
+ &:active {
+ .icon-thumbs-up {
+ background-color: var(--icon-success);
+ }
+
+ .icon-thumbs-down {
+ background-color: var(--icon-critical);
+ }
+ }
+}
diff --git a/client/src/ui/atoms/thumbs/index.tsx b/client/src/ui/atoms/thumbs/index.tsx
new file mode 100644
index 000000000000..e9a8c50b0f01
--- /dev/null
+++ b/client/src/ui/atoms/thumbs/index.tsx
@@ -0,0 +1,131 @@
+import { useEffect, useState } from "react";
+import { THUMBS } from "../../../telemetry/constants";
+import { useGleanClick } from "../../../telemetry/glean-context";
+import { Button } from "../button";
+
+import "./index.scss";
+
+const LOCAL_STORAGE_KEY = "thumbs";
+
+function getPreviouslySubmitted() {
+ try {
+ return JSON.parse(window?.localStorage?.getItem(LOCAL_STORAGE_KEY) ?? "{}");
+ } catch (e) {
+ console.warn("Unable to read thumbs state to localStorage", e);
+ return {};
+ }
+}
+
+function isPreviouslySubmitted(feature: string): boolean {
+ try {
+ return feature in getPreviouslySubmitted();
+ } catch (e) {
+ return false;
+ }
+}
+
+function markPreviouslySubmitted(feature: string, value: boolean) {
+ try {
+ window?.localStorage?.setItem(
+ LOCAL_STORAGE_KEY,
+ JSON.stringify({
+ ...getPreviouslySubmitted(),
+ [feature]: {
+ submitted_at: Date.now(),
+ value,
+ },
+ })
+ );
+ } catch (e) {
+ console.warn("Unable to write thumbs state to localStorage", e);
+ }
+}
+
+export function GleanThumbs({
+ feature,
+ question = "Is this feature useful?",
+ confirmation = "Thank you for your feedback! ❤️",
+ upLabel = "This feature is useful.",
+ downLabel = "This feature is not useful.",
+}: {
+ feature: string;
+ question?: string;
+ confirmation?: string;
+ upLabel?: string;
+ downLabel?: string;
+}) {
+ const [previouslySubmitted, setPreviouslySubmitted] = useState(true);
+ const [submitted, setSubmitted] = useState(false);
+ const gleanClick = useGleanClick();
+
+ useEffect(() => {
+ setPreviouslySubmitted(isPreviouslySubmitted(feature));
+ }, [feature, setPreviouslySubmitted]);
+
+ const handleThumbs = (value: "up" | "down") => {
+ gleanClick(`${THUMBS}: ${feature} -> ${value === "up" ? 1 : 0}`);
+ setSubmitted(true);
+ markPreviouslySubmitted(feature, value === "up");
+ };
+
+ return (
+ <>
+ {!previouslySubmitted && (
+
+ {!submitted ? (
+ <>
+ {question}
+ handleThumbs("up")}
+ onThumbsDown={() => handleThumbs("down")}
+ />
+ >
+ ) : (
+ {confirmation}
+ )}
+
+ )}
+ >
+ );
+}
+
+export function Thumbs({
+ upLabel = "Thumbs up",
+ downLabel = "Thumbs down",
+ onThumbsUp,
+ onThumbsDown,
+ size,
+}: {
+ upLabel?: string;
+ downLabel?: string;
+ onThumbsUp: React.MouseEventHandler;
+ onThumbsDown: React.MouseEventHandler;
+ size?: "small" | "medium";
+}) {
+ return (
+ <>
+
+
+ >
+ );
+}
diff --git a/client/src/ui/molecules/grids/_document-page.scss b/client/src/ui/molecules/grids/_document-page.scss
index 4c63f4cf3aee..24ab1d718587 100644
--- a/client/src/ui/molecules/grids/_document-page.scss
+++ b/client/src/ui/molecules/grids/_document-page.scss
@@ -45,6 +45,7 @@
.sidebar {
align-self: start;
grid-area: sidebar;
+ padding-top: unset;
}
.main-content {
diff --git a/client/src/utils.ts b/client/src/utils.ts
index 114e6cd51b00..6e4e6ce9ab88 100644
--- a/client/src/utils.ts
+++ b/client/src/utils.ts
@@ -110,3 +110,14 @@ export function charSlice(string: string, start?: number, end?: number) {
export function range(start: number, stop: number) {
return [...Array(Math.max(stop - start, 0)).keys()].map((n) => n + start);
}
+
+/**
+ * Used by quicksearch and sidebar filters.
+ */
+export function splitQuery(term: string): string[] {
+ return term
+ .trim()
+ .toLowerCase()
+ .replace(".", " .") // Allows to find `Map.prototype.get()` via `Map.get`.
+ .split(/[ ,]+/);
+}