Repository: GoogleChromeLabs/ps-analysis-tool
Pull request #98 (2023-08-07 13:03:27)

Refactor: Enhancing CookiePanel UI with Styling Adjustments Following Table Sidebar Integration
## Description
This pull request focuses on refining the user interface (UI) of the CookiePanel component by incorporating styling modifications that harmonize with the recent integration of the Table Sidebar feature. With the merge of the Table Sidebar, it became essential to ensure consistent and coherent styling across all components to maintain a seamless user experience.
<!-- What do we want to achieve with this PR? -->

## Relevant Technical Choices

<!-- Please describe your changes. -->

## Testing Instructions

<!--
How can the changes in this PR be verified?
Please provide step-by-step instructions to test the changes.
-->

## Additional Information:

<!-- Any other information. -->

## Screenshot/Screencast

https://github.com/GoogleChromeLabs/ps-analysis-tool/assets/58820001/a079b2b9-a362-4f0c-a131-8b3193b5d76f


<!-- Please provide Screenshot/Screencast, if applicable -->

---

<!--
Example:

Fixes #123
Partially addresses #22
See #834
-->

Fixes #



Patch:
diff --git a/packages/extension/src/view/devtools/app.tsx b/packages/extension/src/view/devtools/app.tsx
index f31fd2db..9bf87213 100644
--- a/packages/extension/src/view/devtools/app.tsx
+++ b/packages/extension/src/view/devtools/app.tsx
@@ -76,7 +76,7 @@ const App: React.FC = () => {
             bottomLeft: false,
             topLeft: false,
           }}
-          className="h-full flex flex-col pt-0.5 border-solid border-r border-b border-gray-300"
+          className="h-full flex flex-col pt-0.5 border border-l-0 border-t-0 border-gray-300"
         >
           <Sidebar
             tabsNames={tabNames}
diff --git a/packages/extension/src/view/devtools/components/sidebar/accordion/index.tsx b/packages/extension/src/view/devtools/components/sidebar/accordion/index.tsx
index fcfda34d..c01fbc9b 100644
--- a/packages/extension/src/view/devtools/components/sidebar/accordion/index.tsx
+++ b/packages/extension/src/view/devtools/components/sidebar/accordion/index.tsx
@@ -79,7 +79,7 @@ const Accordion: React.FC<AccordionProps> = ({
         onKeyDown={(event) => keyboardNavigator(event)}
       >
         <div
-          className={`origin-center transition-transform ${
+          className={`origin-center transition-transform scale-125 p-0.5 ${
             accordionState ? '' : '-rotate-90'
           }`}
           onClick={subMenuSelected}
diff --git a/packages/extension/src/view/devtools/components/tabContent/cookies/cookiesListing/cookieDetails/index.tsx b/packages/extension/src/view/devtools/components/tabContent/cookies/cookiesListing/cookieDetails/index.tsx
index 2e2acc4a..994d1459 100644
--- a/packages/extension/src/view/devtools/components/tabContent/cookies/cookiesListing/cookieDetails/index.tsx
+++ b/packages/extension/src/view/devtools/components/tabContent/cookies/cookiesListing/cookieDetails/index.tsx
@@ -26,9 +26,10 @@ import { useContentPanelStore } from '../../../../../stateProviders/contentPanel
 import Details from './details';
 
 const CookieDetails = () => {
-  const { selectedCookie } = useContentPanelStore(({ state }) => ({
-    selectedCookie: state.selectedCookie,
+  const { selectedFrameCookie } = useContentPanelStore(({ state }) => ({
+    selectedFrameCookie: state.selectedFrameCookie,
   }));
+  const selectedCookie = Object.values(selectedFrameCookie ?? {})[0];
 
   return (
     <div data-testid="cookie-card" className="h-full min-w-[10rem]">
@@ -37,7 +38,7 @@ const CookieDetails = () => {
       ) : (
         <div className="h-full p-8 flex items-center">
           <p className="text-lg w-full font-bold text-granite-gray text-center">
-            Select a cookie to preview its value
+            Select cookies to preview its value
           </p>
         </div>
       )}
diff --git a/packages/extension/src/view/devtools/components/tabContent/cookies/cookiesListing/cookieTable/index.tsx b/packages/extension/src/view/devtools/components/tabContent/cookies/cookiesListing/cookieTable/index.tsx
index bfce0e9a..6a22f8eb 100644
--- a/packages/extension/src/view/devtools/components/tabContent/cookies/cookiesListing/cookieTable/index.tsx
+++ b/packages/extension/src/view/devtools/components/tabContent/cookies/cookiesListing/cookieTable/index.tsx
@@ -17,7 +17,7 @@
 /**
  * External dependencies.
  */
-import React, { useMemo } from 'react';
+import React, { useCallback, useEffect, useMemo } from 'react';
 import {
   useReactTable,
   getCoreRowModel,
@@ -35,6 +35,7 @@ import type { CookieTableData } from '../../../../../stateProviders/syncCookieSt
 
 export interface CookieTableProps {
   cookies: CookieTableData[];
+  selectedFrame: string | null;
 }
 
 const tableColumns: ColumnDef<CookieTableData>[] = [
@@ -144,19 +145,29 @@ const tableColumns: ColumnDef<CookieTableData>[] = [
   },
 ];
 
-const CookieTable = ({ cookies: data }: CookieTableProps) => {
+const CookieTable = ({ cookies: data, selectedFrame }: CookieTableProps) => {
   const {
-    selectedCookie,
-    setSelectedCookie,
+    selectedFrameCookie,
+    setSelectedFrameCookie,
     tableColumnSize,
     tableContainerRef,
   } = useContentPanelStore(({ state, actions }) => ({
-    selectedCookie: state.selectedCookie,
-    setSelectedCookie: actions.setSelectedCookie,
+    selectedFrameCookie: state.selectedFrameCookie || {},
+    setSelectedFrameCookie: actions.setSelectedFrameCookie,
     tableColumnSize: state.tableColumnSize,
     tableContainerRef: state.tableContainerRef,
   }));
 
+  useEffect(() => {
+    if (
+      selectedFrame &&
+      selectedFrameCookie &&
+      selectedFrameCookie[selectedFrame] === undefined
+    ) {
+      setSelectedFrameCookie(null);
+    }
+  }, [selectedFrameCookie, selectedFrame, setSelectedFrameCookie]);
+
   const columns = useMemo(
     () =>
       tableColumns.map((column) => ({
@@ -175,6 +186,15 @@ const CookieTable = ({ cookies: data }: CookieTableProps) => {
     getSortedRowModel: getSortedRowModel(),
   });
 
+  const onRowClick = useCallback(
+    (cookieData: CookieTableData) => {
+      setSelectedFrameCookie({
+        [selectedFrame as string]: cookieData,
+      });
+    },
+    [selectedFrame, setSelectedFrameCookie]
+  );
+
   return (
     <div
       ref={tableContainerRef}
@@ -182,8 +202,10 @@ const CookieTable = ({ cookies: data }: CookieTableProps) => {
     >
       <Table
         table={table}
-        selectedKey={selectedCookie?.parsedCookie.name}
-        onRowClick={setSelectedCookie}
+        selectedKey={
+          Object.values(selectedFrameCookie ?? {})[0]?.parsedCookie.name
+        }
+        onRowClick={onRowClick}
       />
     </div>
   );
diff --git a/packages/extension/src/view/devtools/components/tabContent/cookies/cookiesListing/index.tsx b/packages/extension/src/view/devtools/components/tabContent/cookies/cookiesListing/index.tsx
index 699d178c..1ede76a7 100644
--- a/packages/extension/src/view/devtools/components/tabContent/cookies/cookiesListing/index.tsx
+++ b/packages/extension/src/view/devtools/components/tabContent/cookies/cookiesListing/index.tsx
@@ -66,7 +66,10 @@ const CookiesListing = () => {
           left: false,
         }}
       >
-        <CookieTable cookies={calculatedCookies} />
+        <CookieTable
+          cookies={calculatedCookies}
+          selectedFrame={selectedFrame}
+        />
       </Resizable>
       <div className="w-full h-full bg-white border-2 border-gray-300 shadow overflow-auto">
         <CookieDetails />
diff --git a/packages/extension/src/view/devtools/components/tabContent/cookies/index.tsx b/packages/extension/src/view/devtools/components/tabContent/cookies/index.tsx
index 4cdf863d..69f4e44a 100644
--- a/packages/extension/src/view/devtools/components/tabContent/cookies/index.tsx
+++ b/packages/extension/src/view/devtools/components/tabContent/cookies/index.tsx
@@ -33,7 +33,7 @@ const Cookies = () => {
 
   return (
     <div
-      className={`h-full border border-american-silver ${
+      className={`h-full ${
         selectedFrame ? '' : 'flex items-center justify-center'
       }`}
       data-testid="cookies-content"
diff --git a/packages/extension/src/view/devtools/components/table/index.tsx b/packages/extension/src/view/devtools/components/table/index.tsx
index 1ba17cb5..99204705 100644
--- a/packages/extension/src/view/devtools/components/table/index.tsx
+++ b/packages/extension/src/view/devtools/components/table/index.tsx
@@ -60,7 +60,7 @@ const Table = ({ table, selectedKey, onRowClick }: TableProps) => {
         columns={table.getAllLeafColumns()}
         position={columnPosition}
       />
-      <table className="w-full">
+      <table className="w-full h-full">
         <TableHeader
           headerGroups={table.getHeaderGroups()}
           setColumnPosition={setColumnPosition}
@@ -70,6 +70,7 @@ const Table = ({ table, selectedKey, onRowClick }: TableProps) => {
           rows={table.getRowModel().rows}
           selectedKey={selectedKey}
           onRowClick={onRowClick}
+          emptyRowCellCount={table.getAllColumns().length}
         />
       </table>
     </>
diff --git a/packages/extension/src/view/devtools/components/table/tableBody/index.tsx b/packages/extension/src/view/devtools/components/table/tableBody/index.tsx
index 0432cc3b..5b6a49c8 100644
--- a/packages/extension/src/view/devtools/components/table/tableBody/index.tsx
+++ b/packages/extension/src/view/devtools/components/table/tableBody/index.tsx
@@ -30,9 +30,15 @@ interface TableBodyProps {
   rows: Row<TableData>[];
   selectedKey: string | undefined;
   onRowClick: (key: TableData) => void;
+  emptyRowCellCount: number;
 }
 
-const TableBody = ({ rows, selectedKey, onRowClick }: TableBodyProps) => {
+const TableBody = ({
+  rows,
+  selectedKey,
+  onRowClick,
+  emptyRowCellCount,
+}: TableBodyProps) => {
   const tableBodyRef = useRef(null);
 
   const handleKeyDown = useCallback(
@@ -67,7 +73,7 @@ const TableBody = ({ rows, selectedKey, onRowClick }: TableBodyProps) => {
   );
 
   return (
-    <tbody ref={tableBodyRef}>
+    <tbody ref={tableBodyRef} className="h-full">
       {rows.map((row, index) => (
         <BodyRow
           key={row.id}
@@ -78,6 +84,26 @@ const TableBody = ({ rows, selectedKey, onRowClick }: TableBodyProps) => {
           onKeyDown={handleKeyDown}
         />
       ))}
+      <tr className="h-5">
+        {[...Array(emptyRowCellCount)].map((_, index) => (
+          <td
+            key={index}
+            className={`border border-y-0 border-american-silver px-1 py-px ${
+              index === 0 ? 'pl-5' : ''
+            } ${rows.length % 2 ? 'bg-anti-flash-white' : ''}`}
+          ></td>
+        ))}
+      </tr>
+      <tr className="h-full">
+        {[...Array(emptyRowCellCount)].map((_, index) => (
+          <td
+            key={index}
+            className={`h-full border border-y-0 border-american-silver px-1 py-px ${
+              index === 0 ? 'pl-5' : ''
+            }`}
+          ></td>
+        ))}
+      </tr>
     </tbody>
   );
 };
diff --git a/packages/extension/src/view/devtools/stateProviders/contentPanelStore/index.tsx b/packages/extension/src/view/devtools/stateProviders/contentPanelStore/index.tsx
index 1448eadf..693f4153 100644
--- a/packages/extension/src/view/devtools/stateProviders/contentPanelStore/index.tsx
+++ b/packages/extension/src/view/devtools/stateProviders/contentPanelStore/index.tsx
@@ -27,28 +27,34 @@ import { useContextSelector, createContext } from 'use-context-selector';
 /**
  * Internal dependencies.
  */
-import { useCookieStore, type CookieTableData } from '../syncCookieStore';
+import { type CookieTableData } from '../syncCookieStore';
 
 export interface ContentPanelStore {
   state: {
-    selectedCookie: CookieTableData | null;
+    selectedFrameCookie: {
+      [frame: string]: CookieTableData | null;
+    } | null;
     tableColumnSize: number;
-    tableContainerRef: React.RefObject<HTMLTableElement>;
+    tableContainerRef: React.RefObject<HTMLDivElement>;
   };
   actions: {
-    setSelectedCookie: (cookie: CookieTableData | null) => void;
+    setSelectedFrameCookie: (
+      cookie: {
+        [frame: string]: CookieTableData | null;
+      } | null
+    ) => void;
     setTableColumnSize: (size: number) => void;
   };
 }
 
 const initialState: ContentPanelStore = {
   state: {
-    selectedCookie: null,
+    selectedFrameCookie: null,
     tableColumnSize: 100,
-    tableContainerRef: React.createRef<HTMLTableElement>(),
+    tableContainerRef: React.createRef<HTMLDivElement>(),
   },
   actions: {
-    setSelectedCookie: () => {
+    setSelectedFrameCookie: () => {
       // Do nothing.
     },
     setTableColumnSize: () => {
@@ -60,21 +66,12 @@ const initialState: ContentPanelStore = {
 export const Context = createContext<ContentPanelStore>(initialState);
 
 export const Provider = ({ children }: PropsWithChildren) => {
-  const [selectedCookie, setSelectedCookie] = useState<CookieTableData | null>(
-    null
-  );
-  const { selectedFrame } = useCookieStore(({ state }) => ({
-    selectedFrame: state.selectedFrame,
-  }));
+  const [selectedFrameCookie, setSelectedFrameCookie] =
+    useState<ContentPanelStore['state']['selectedFrameCookie']>(null);
 
-  useEffect(() => {
-    if (!selectedFrame) {
-      setSelectedCookie(null);
-    }
-  }, [selectedFrame]);
   const [tableColumnSize, setTableColumnSize] = useState(100);
 
-  const tableContainerRef = useRef<HTMLTableElement>(null);
+  const tableContainerRef = useRef<HTMLDivElement>(null);
 
   useEffect(() => {
     const handleResize = () => {
@@ -93,12 +90,12 @@ export const Provider = ({ children }: PropsWithChildren) => {
     <Context.Provider
       value={{
         state: {
-          selectedCookie,
+          selectedFrameCookie,
           tableColumnSize,
           tableContainerRef,
         },
         actions: {
-          setSelectedCookie,
+          setSelectedFrameCookie,
           setTableColumnSize,
         },
       }}
diff --git a/packages/extension/src/view/devtools/stateProviders/syncCookieStore/index.tsx b/packages/extension/src/view/devtools/stateProviders/syncCookieStore/index.tsx
index 3269878c..92b865eb 100644
--- a/packages/extension/src/view/devtools/stateProviders/syncCookieStore/index.tsx
+++ b/packages/extension/src/view/devtools/stateProviders/syncCookieStore/index.tsx
@@ -200,6 +200,7 @@ export const Provider = ({ children }: PropsWithChildren) => {
       if (tabId === _tabId && changeInfo.url) {
         setTabUrl(changeInfo.url);
       }
+      setSelectedFrame(null);
       await getAllFramesForCurrentTab(_tabId);
     },
     [tabId, getAllFramesForCurrentTab]


Test patch:
diff --git a/packages/extension/src/view/devtools/components/tabContent/cookies/tests/cookieTab.tsx b/packages/extension/src/view/devtools/components/tabContent/cookies/tests/cookieTab.tsx
index e90577e6..f044ff6e 100644
--- a/packages/extension/src/view/devtools/components/tabContent/cookies/tests/cookieTab.tsx
+++ b/packages/extension/src/view/devtools/components/tabContent/cookies/tests/cookieTab.tsx
@@ -166,7 +166,10 @@ jest.mock('../../../../stateProviders/syncCookieStore', () => {
 jest.mock('../../../../stateProviders/contentPanelStore');
 const mockUseContentPanelStore = useContentPanelStore as jest.Mock;
 mockUseContentPanelStore.mockReturnValue({
-  selectedCookie: mockResponse.tabCookies[uncategorised1pCookie.name],
+  selectedFrameCookie: {
+    1: mockResponse.tabCookies[uncategorised1pCookie.name],
+  },
+  setSelectedFrameCookie: jest.fn(),
   tableContainerRef: { current: null },
   tableColumnSize: 100,
   setTableColumnSize: jest.fn(),
@@ -261,7 +264,7 @@ describe('CookieTab', () => {
 
   it('should render a cookie card with placeholder text when no cookie is selected', async () => {
     mockUseContentPanelStore.mockReturnValue({
-      selectedCookie: null,
+      selectedFrameCookie: null,
       tableContainerRef: { current: null },
       tableColumnSize: 100,
       setTableColumnSize: jest.fn(),
@@ -270,7 +273,7 @@ describe('CookieTab', () => {
     render(<CookieDetails />);
 
     expect(
-      await screen.findByText('Select a cookie to preview its value')
+      await screen.findByText('Select cookies to preview its value')
     ).toBeInTheDocument();
   });
 
@@ -302,7 +305,9 @@ describe('CookieTab', () => {
       mockResponse.tabCookies[Object.keys(mockResponse.tabCookies)[0]];
 
     mockUseContentPanelStore.mockReturnValue({
-      selectedCookie: firstCookie,
+      selectedFrameCookie: {
+        1: firstCookie,
+      },
       tableContainerRef: { current: null },
       tableColumnSize: 100,
       setTableColumnSize: jest.fn(),
@@ -320,7 +325,9 @@ describe('CookieTab', () => {
 
   it('should show a cookie card with the description about cookie', async () => {
     mockUseContentPanelStore.mockReturnValue({
-      selectedCookie: mockResponse.tabCookies[known1pCookie.name],
+      selectedFrameCookie: {
+        1: mockResponse.tabCookies[known1pCookie.name],
+      },
       tableContainerRef: { current: null },
       tableColumnSize: 100,
       setTableColumnSize: jest.fn(),
@@ -339,7 +346,9 @@ describe('CookieTab', () => {
 
   it('should show a cookie card with no description about cookie', async () => {
     mockUseContentPanelStore.mockReturnValue({
-      selectedCookie: mockResponse.tabCookies[uncategorised1pCookie.name],
+      selectedFrameCookie: {
+        1: mockResponse.tabCookies[uncategorised1pCookie.name],
+      },
       tableContainerRef: { current: null },
       tableColumnSize: 100,
       setTableColumnSize: jest.fn(),
@@ -357,8 +366,8 @@ describe('CookieTab', () => {
   it('should get the cookie object when row is clicked or Arrow up/down pressed', async () => {
     const setStateMock = jest.fn();
     mockUseContentPanelStore.mockReturnValue({
-      selectedCookie: null,
-      setSelectedCookie: setStateMock,
+      selectedFrameCookie: null,
+      setSelectedFrameCookie: setStateMock,
       tableContainerRef: {
         current: {
           offsetWidth: 1000,
@@ -378,14 +387,16 @@ describe('CookieTab', () => {
     const row = (await screen.findAllByTestId('body-row'))[0];
     fireEvent.click(row);
 
-    expect(setStateMock).toHaveBeenCalledWith(
-      mockResponse.tabCookies[uncategorised1pCookie.name]
-    );
+    expect(setStateMock).toHaveBeenCalledWith({
+      'https://edition.cnn.com/':
+        mockResponse.tabCookies[uncategorised1pCookie.name],
+    });
 
     fireEvent.keyDown(row, { key: 'ArrowDown', code: 'ArrowDown' });
 
-    expect(setStateMock).toHaveBeenCalledWith(
-      mockResponse.tabCookies[uncategorised1pCookie.name]
-    );
+    expect(setStateMock).toHaveBeenCalledWith({
+      'https://edition.cnn.com/':
+        mockResponse.tabCookies[uncategorised1pCookie.name],
+    });
   });
 });