From c64ce9507ed9931a25d4be793eb36a0f27d20e87 Mon Sep 17 00:00:00 2001
From: nick-funk <code@nickfunk.com>
Date: Sat, 27 Jul 2024 01:40:52 -0600
Subject: [PATCH 1/3] stack tenor gif results tightly in a cascade grid

---
 .../tabs/Comments/TenorInput/TenorGrid.css    |  34 ++++
 .../tabs/Comments/TenorInput/TenorGrid.tsx    | 158 ++++++++++++++++++
 .../tabs/Comments/TenorInput/TenorInput.css   |  29 ----
 .../tabs/Comments/TenorInput/TenorInput.tsx   |  37 +---
 4 files changed, 201 insertions(+), 57 deletions(-)
 create mode 100644 client/src/core/client/stream/tabs/Comments/TenorInput/TenorGrid.css
 create mode 100644 client/src/core/client/stream/tabs/Comments/TenorInput/TenorGrid.tsx

diff --git a/client/src/core/client/stream/tabs/Comments/TenorInput/TenorGrid.css b/client/src/core/client/stream/tabs/Comments/TenorInput/TenorGrid.css
new file mode 100644
index 0000000000..5c2772950b
--- /dev/null
+++ b/client/src/core/client/stream/tabs/Comments/TenorInput/TenorGrid.css
@@ -0,0 +1,34 @@
+.grid {
+  max-height: 300px;
+  overflow: auto;
+}
+
+.gridColumns {
+  display: flex;
+  flex-direction: row;
+  justify-content: center;
+}
+
+.gridColumn {
+  display: flex;
+  flex-direction: column;
+}
+
+.gridControls {
+  width: 100%;
+  display: flex;
+  justify-content: center;
+  align-items: center;
+}
+
+.gridItem {
+  width: 85px;
+  background: var(--palette-background-body);
+  border-style: none;
+  padding: 0px;
+}
+
+.gridImage {
+  width: 100%;
+  height: auto;
+}
diff --git a/client/src/core/client/stream/tabs/Comments/TenorInput/TenorGrid.tsx b/client/src/core/client/stream/tabs/Comments/TenorInput/TenorGrid.tsx
new file mode 100644
index 0000000000..a12bc4642a
--- /dev/null
+++ b/client/src/core/client/stream/tabs/Comments/TenorInput/TenorGrid.tsx
@@ -0,0 +1,158 @@
+import { Localized } from "@fluent/react/compat";
+import React, {
+  FunctionComponent,
+  useCallback,
+  useEffect,
+  useMemo,
+  useRef,
+  useState,
+} from "react";
+
+import { useCoralContext } from "coral-framework/lib/bootstrap";
+import { Button } from "coral-ui/components/v2";
+
+import { GifResult } from "./TenorInput";
+
+import styles from "./TenorGrid.css";
+
+interface GridItemProps {
+  gif: GifResult;
+  onSelect: (gif: GifResult) => void;
+}
+
+const TenorGridItem: FunctionComponent<GridItemProps> = ({ gif, onSelect }) => {
+  const onClick = useCallback(() => {
+    onSelect(gif);
+  }, [gif, onSelect]);
+
+  return (
+    <button className={styles.gridItem} onClick={onClick}>
+      <img className={styles.gridImage} alt={gif.title} src={gif.preview}></img>
+    </button>
+  );
+};
+
+interface GridColumnsProps {
+  gifs: GifResult[];
+  onSelectGif: (gif: GifResult) => void;
+  numColumns: number;
+}
+
+const TenorGridColumns: FunctionComponent<GridColumnsProps> = ({
+  gifs,
+  onSelectGif,
+  numColumns,
+}) => {
+  const columns = useMemo(() => {
+    const resultColumns: GifResult[][] = [];
+    for (let i = 0; i < numColumns; i++) {
+      resultColumns.push(new Array<GifResult>());
+    }
+
+    let columnIndex = 0;
+    // eslint-disable-next-line @typescript-eslint/prefer-for-of
+    for (let j = 0; j < gifs.length; j++) {
+      const column = resultColumns[columnIndex];
+      const gif = gifs[j];
+
+      column.push(gif);
+
+      columnIndex++;
+      if (columnIndex >= numColumns) {
+        columnIndex = 0;
+      }
+    }
+
+    return resultColumns;
+  }, [gifs, numColumns]);
+
+  return (
+    <div className={styles.gridColumns}>
+      {columns.map((colGifs, colIndex) => {
+        return (
+          <div
+            key={`tenor-gif-result-column-${colIndex}`}
+            className={styles.gridColumn}
+          >
+            {colGifs &&
+              colGifs.map((gif, index) => {
+                return (
+                  <TenorGridItem
+                    key={`${gif.id}-${index}`}
+                    gif={gif}
+                    onSelect={onSelectGif}
+                  />
+                );
+              })}
+          </div>
+        );
+      })}
+    </div>
+  );
+};
+
+interface Props {
+  gifs: GifResult[];
+  showLoadMore?: boolean;
+
+  onSelectGif: (gif: GifResult) => void;
+  onLoadMore: () => void;
+}
+
+const TenorGrid: FunctionComponent<Props> = ({
+  gifs,
+  showLoadMore,
+  onSelectGif,
+  onLoadMore,
+}) => {
+  const { window } = useCoralContext();
+
+  const gridRef = useRef<HTMLDivElement>(null);
+  const [cols, setCols] = useState<number>(0);
+
+  const resizeGrid = useCallback(() => {
+    if (!gridRef || !gridRef.current) {
+      setCols(0);
+      return;
+    }
+
+    const rect = gridRef.current.getBoundingClientRect();
+    const numCols = rect.width / 90;
+
+    setCols(numCols);
+  }, [gridRef, setCols]);
+
+  useEffect(() => {
+    window.requestAnimationFrame(resizeGrid);
+    window.addEventListener("resize", resizeGrid);
+
+    return () => {
+      window.removeEventListener("resize", resizeGrid);
+    };
+    // include gifs so we re-calc grid col's on gif change
+    // eslint-disable-next-line react-hooks/exhaustive-deps
+  }, [gifs]);
+
+  return (
+    <div className={styles.grid}>
+      {gifs && cols > 0 && (
+        <TenorGridColumns
+          gifs={gifs}
+          onSelectGif={onSelectGif}
+          numColumns={cols}
+        />
+      )}
+      {showLoadMore && (
+        <div className={styles.gridControls} ref={gridRef}>
+          <Localized id="comments-postComment-gifSearch-search-loadMore">
+            <Button color="stream" onClick={onLoadMore}>
+              Load More
+            </Button>
+          </Localized>
+        </div>
+      )}
+    </div>
+  );
+};
+
+export default TenorGrid;
diff --git a/client/src/core/client/stream/tabs/Comments/TenorInput/TenorInput.css b/client/src/core/client/stream/tabs/Comments/TenorInput/TenorInput.css
index 201a68c1a7..1446ef3e8c 100644
--- a/client/src/core/client/stream/tabs/Comments/TenorInput/TenorInput.css
+++ b/client/src/core/client/stream/tabs/Comments/TenorInput/TenorInput.css
@@ -29,35 +29,6 @@
   max-width: 100%;
 }
 
-.grid {
-  max-height: 300px;
-  overflow: auto;
-
-  display: flex;
-  flex-wrap: wrap;
-  justify-content: center;
-  gap: 8px;
-}
-
-.gridControls {
-  width: 100%;
-  display: flex;
-  justify-content: center;
-  align-items: center;
-}
-
-.gridItem {
-  width: 85px;
-  background: var(--palette-background-body);
-  border-style: none;
-  padding: 0px;
-}
-
-.gridImage {
-  width: 100%;
-  height: auto;
-}
-
 .input {
   border-top-right-radius: 0;
   border-bottom-right-radius: 0;
diff --git a/client/src/core/client/stream/tabs/Comments/TenorInput/TenorInput.tsx b/client/src/core/client/stream/tabs/Comments/TenorInput/TenorInput.tsx
index ae9e8e90de..5d0f62e6fd 100644
--- a/client/src/core/client/stream/tabs/Comments/TenorInput/TenorInput.tsx
+++ b/client/src/core/client/stream/tabs/Comments/TenorInput/TenorInput.tsx
@@ -18,6 +18,7 @@ import { ButtonSvgIcon, SearchIcon } from "coral-ui/components/icons";
 import { Button, HorizontalGutter, TextField } from "coral-ui/components/v2";
 
 import TenorAttribution from "./TenorAttribution";
+import TenorGrid from "./TenorGrid";
 
 import styles from "./TenorInput.css";
 
@@ -181,34 +182,14 @@ const TenorInput: FunctionComponent<Props> = ({ onSelect }) => {
           }
           ref={inputRef}
         />
-        <div className={styles.grid}>
-          {query &&
-            gifs &&
-            gifs.map((gif, index) => {
-              return (
-                <button
-                  className={styles.gridItem}
-                  key={`${gif.id}-${index}`}
-                  onClick={() => onGifClick(gif)}
-                >
-                  <img
-                    className={styles.gridImage}
-                    alt={gif.title}
-                    src={gif.preview}
-                  ></img>
-                </button>
-              );
-            })}
-          {next && gifs && gifs.length > 0 && query?.length > 0 && (
-            <div className={styles.gridControls}>
-              <Localized id="comments-postComment-gifSearch-search-loadMore">
-                <Button color="stream" onClick={onLoadMore}>
-                  Load More
-                </Button>
-              </Localized>
-            </div>
-          )}
-        </div>
+        <TenorGrid
+          gifs={query ? gifs : []}
+          showLoadMore={
+            !!(next && gifs && gifs.length > 0 && query?.length > 0)
+          }
+          onSelectGif={onGifClick}
+          onLoadMore={onLoadMore}
+        />
         <TenorAttribution />
       </HorizontalGutter>
     </div>

From 921a71b318be5e17ab8107daf60fa97f96d9f692 Mon Sep 17 00:00:00 2001
From: nick-funk <code@nickfunk.com>
Date: Sat, 27 Jul 2024 01:47:34 -0600
Subject: [PATCH 2/3] flex center tenor gif search result images

---
 .../core/client/stream/tabs/Comments/TenorInput/TenorGrid.css  | 3 +++
 1 file changed, 3 insertions(+)

diff --git a/client/src/core/client/stream/tabs/Comments/TenorInput/TenorGrid.css b/client/src/core/client/stream/tabs/Comments/TenorInput/TenorGrid.css
index 5c2772950b..e3b40ccca6 100644
--- a/client/src/core/client/stream/tabs/Comments/TenorInput/TenorGrid.css
+++ b/client/src/core/client/stream/tabs/Comments/TenorInput/TenorGrid.css
@@ -26,6 +26,9 @@
   background: var(--palette-background-body);
   border-style: none;
   padding: 0px;
+
+  display: flex;
+  justify-content: center;
 }
 
 .gridImage {

From 426039d0da9eadb40d04b0d3d26de698927625ba Mon Sep 17 00:00:00 2001
From: nick-funk <code@nickfunk.com>
Date: Wed, 31 Jul 2024 09:20:42 -0600
Subject: [PATCH 3/3] set padding on grid items

---
 .../core/client/stream/tabs/Comments/TenorInput/TenorGrid.css   | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/client/src/core/client/stream/tabs/Comments/TenorInput/TenorGrid.css b/client/src/core/client/stream/tabs/Comments/TenorInput/TenorGrid.css
index e3b40ccca6..458806cd34 100644
--- a/client/src/core/client/stream/tabs/Comments/TenorInput/TenorGrid.css
+++ b/client/src/core/client/stream/tabs/Comments/TenorInput/TenorGrid.css
@@ -25,7 +25,7 @@
   width: 85px;
   background: var(--palette-background-body);
   border-style: none;
-  padding: 0px;
+  padding: 2px;
 
   display: flex;
   justify-content: center;