Repository: GoogleChromeLabs/ps-analysis-tool
Pull request #96 (2023-08-04 02:36:37)

Feature: Cookies Landing Page - Part-2
## Description

Create few components for cookies landing page and refactor code.

## Relevant Technical Choices

- Refactor code and move Cookie Listing to its own component.
- Add test coverage
- Create stories

## Testing Instructions

<!--
How can the changes in this PR be verified?
Please provide step-by-step instructions to test the changes.
-->

## Additional Information:

This PR doesn't create the actual UI of the cookies landing page, therefore QA is not required yet.

## Screenshot/Screencast

<!-- Please provide Screenshot/Screencast, if applicable -->

---

<!--
Example:

Fixes #123
Partially addresses #22
See #834
-->

Partially addresses https://github.com/GoogleChromeLabs/ps-analysis-tool/issues/66



Patch:
diff --git a/package-lock.json b/package-lock.json
index 52e02373..007daf64 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -9323,6 +9323,11 @@
 			"dev": true,
 			"license": "MIT"
 		},
+		"node_modules/classnames": {
+			"version": "2.3.2",
+			"resolved": "https://registry.npmjs.org/classnames/-/classnames-2.3.2.tgz",
+			"integrity": "sha512-CSbhY4cFEJRe6/GQzIk5qXZ4Jeg5pcsP7b5peFSDpffpe1cqjASH/n9UTjBwOp6XpMSTwQ8Za2K5V02ueA7Tmw=="
+		},
 		"node_modules/clean-css": {
 			"version": "5.3.2",
 			"dev": true,
@@ -22799,6 +22804,7 @@
 			"license": "Apache-2.0",
 			"dependencies": {
 				"@tanstack/react-table": "^8.9.3",
+				"classnames": "^2.3.2",
 				"re-resizable": "^6.9.9",
 				"react": "^18.2.0",
 				"react-dom": "^18.2.0",
@@ -24164,6 +24170,7 @@
 			"version": "file:packages/extension",
 			"requires": {
 				"@tanstack/react-table": "^8.9.3",
+				"classnames": "^2.3.2",
 				"re-resizable": "^6.9.9",
 				"react": "^18.2.0",
 				"react-dom": "^18.2.0",
@@ -28956,6 +28963,11 @@
 			"version": "1.2.3",
 			"dev": true
 		},
+		"classnames": {
+			"version": "2.3.2",
+			"resolved": "https://registry.npmjs.org/classnames/-/classnames-2.3.2.tgz",
+			"integrity": "sha512-CSbhY4cFEJRe6/GQzIk5qXZ4Jeg5pcsP7b5peFSDpffpe1cqjASH/n9UTjBwOp6XpMSTwQ8Za2K5V02ueA7Tmw=="
+		},
 		"clean-css": {
 			"version": "5.3.2",
 			"dev": true,
diff --git a/packages/extension/package.json b/packages/extension/package.json
index 01197092..053bf4eb 100644
--- a/packages/extension/package.json
+++ b/packages/extension/package.json
@@ -13,6 +13,7 @@
   "homepage": "https://github.com/GoogleChromeLabs/ps-analysis-tool#readme",
   "dependencies": {
     "@tanstack/react-table": "^8.9.3",
+    "classnames": "^2.3.2",
     "re-resizable": "^6.9.9",
     "react": "^18.2.0",
     "react-dom": "^18.2.0",
diff --git a/packages/extension/src/view/design-system/components/circle/circleEmpty.tsx b/packages/extension/src/view/design-system/components/circle/circleEmpty.tsx
new file mode 100644
index 00000000..88de27c6
--- /dev/null
+++ b/packages/extension/src/view/design-system/components/circle/circleEmpty.tsx
@@ -0,0 +1,39 @@
+/*
+ * Copyright 2023 Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+/**
+ * External dependencies.
+ */
+import React from 'react';
+
+/**
+ * Internal dependencies.
+ */
+import { COLOR_MAP } from '../../theme/colors';
+
+interface CircleEmptyProps {
+  color: string;
+}
+
+const CircleEmpty = ({ color = COLOR_MAP.mediumGray }: CircleEmptyProps) => (
+  <div
+    className="w-2.5 h-2.5 rounded-full flex items-center justify-center"
+    style={{ backgroundColor: color }}
+  >
+    <div className="w-1.5 h-1.5 rounded-full flex items-center justify-center bg-white" />
+  </div>
+);
+
+export default CircleEmpty;
diff --git a/packages/extension/src/view/design-system/components/circle/index.tsx b/packages/extension/src/view/design-system/components/circle/index.tsx
index 57704517..fdb0ab56 100644
--- a/packages/extension/src/view/design-system/components/circle/index.tsx
+++ b/packages/extension/src/view/design-system/components/circle/index.tsx
@@ -24,11 +24,9 @@ interface CircleProps {
 
 const Circle = ({ color }: CircleProps) => (
   <div
-    className="w-4 h-4 rounded-full flex items-center justify-center mr-2"
+    className="w-2.5 h-2.5 rounded-full flex items-center justify-center"
     style={{ backgroundColor: color }}
-  >
-    <div className="w-2 h-2 rounded-full flex items-center justify-center bg-white" />
-  </div>
+  />
 );
 
 export default Circle;
diff --git a/packages/extension/src/view/design-system/components/circle/stories/circleEmpty.stories.tsx b/packages/extension/src/view/design-system/components/circle/stories/circleEmpty.stories.tsx
new file mode 100644
index 00000000..1a74f755
--- /dev/null
+++ b/packages/extension/src/view/design-system/components/circle/stories/circleEmpty.stories.tsx
@@ -0,0 +1,34 @@
+/*
+ * Copyright 2023 Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+/**
+ * External dependencies.
+ */
+import type { Meta, StoryObj } from '@storybook/react';
+
+/**
+ * Internal dependencies.
+ */
+import CircleEmpty from '../circleEmpty';
+
+const meta: Meta<typeof CircleEmpty> = {
+  title: 'Extension/DesignSystem/CircleEmpty',
+  component: CircleEmpty,
+  tags: ['autodocs'],
+};
+
+export default meta;
+
+export const Primary: StoryObj<typeof meta> = {};
diff --git a/packages/extension/src/view/design-system/components/circlePieChart/emptyCirclePieChart.tsx b/packages/extension/src/view/design-system/components/circlePieChart/emptyCirclePieChart.tsx
new file mode 100644
index 00000000..df6fd6be
--- /dev/null
+++ b/packages/extension/src/view/design-system/components/circlePieChart/emptyCirclePieChart.tsx
@@ -0,0 +1,75 @@
+/*
+ * Copyright 2023 Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+/**
+ * External dependencies.
+ */
+import React from 'react';
+import { VictoryPie } from 'victory';
+import classNames from 'classnames';
+
+/**
+ * Internal dependencies.
+ */
+import { COLOR_MAP } from '../../theme/colors';
+
+interface EmptyCirclePieChartProps {
+  fallbackText?: string;
+  title?: string;
+  isPrimary?: boolean;
+}
+
+const EmptyCirclePieChart = ({
+  fallbackText,
+  title,
+  isPrimary = true,
+}: EmptyCirclePieChartProps) => {
+  const containerWidthClass = isPrimary ? 'w-16' : 'w-8';
+  const centerTitleClasses = isPrimary
+    ? 'text-xs leading-4'
+    : 'text-[7px] leading-[7px]'; // Font size and line height are added as an exception to handle edge case.
+
+  return (
+    <>
+      <div
+        className={classNames(
+          'h-full max-w-xs inline-block align-bottom',
+          containerWidthClass
+        )}
+      >
+        <div className="w-full h-full relative">
+          <VictoryPie
+            padding={0}
+            innerRadius={0}
+            colorScale={[COLOR_MAP.brightGray]}
+          />
+          <p
+            className={classNames(
+              'absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 text-center text-raisin-black opacity-40',
+              centerTitleClasses
+            )}
+          >
+            {fallbackText || 'Not Found'}
+          </p>
+        </div>
+      </div>
+      {title && (
+        <p className="text-xs text-center font-semibold mt-2">{title}</p>
+      )}
+    </>
+  );
+};
+
+export default EmptyCirclePieChart;
diff --git a/packages/extension/src/view/design-system/components/circlePieChart/index.tsx b/packages/extension/src/view/design-system/components/circlePieChart/index.tsx
index d7e2aaef..19dfd010 100644
--- a/packages/extension/src/view/design-system/components/circlePieChart/index.tsx
+++ b/packages/extension/src/view/design-system/components/circlePieChart/index.tsx
@@ -18,28 +18,78 @@
  */
 import React from 'react';
 import { VictoryPie } from 'victory';
+import classNames from 'classnames';
+
+/**
+ * Internal dependencies.
+ */
+import EmptyCirclePieChart from './emptyCirclePieChart';
 
 interface CirclePieChartProps {
   centerCount: number;
   data: { count: number; color: string }[];
+  title?: string;
+  isPrimary?: boolean;
+  fallbackText?: string;
 }
-const CirclePieChart = ({ centerCount, data }: CirclePieChartProps) => {
+
+export const MAX_COUNT = 999;
+
+const CirclePieChart = ({
+  centerCount,
+  data,
+  title,
+  isPrimary = true,
+  fallbackText,
+}: CirclePieChartProps) => {
+  let centerTitleClasses = centerCount <= MAX_COUNT ? 'text-2xl' : 'text-l';
+  const containerWidthClass = isPrimary ? 'w-16' : 'w-8';
+
+  if (!isPrimary) {
+    centerTitleClasses = centerCount <= MAX_COUNT ? 'text-xs' : 'text-xxxs';
+  }
+
+  if (centerCount <= 0) {
+    return (
+      <EmptyCirclePieChart
+        title={title}
+        isPrimary={isPrimary}
+        fallbackText={fallbackText}
+      />
+    );
+  }
+
   return (
-    <div className="w-full h-full max-w-xs">
-      <div className="w-full h-full relative">
-        <VictoryPie
-          padding={0}
-          innerRadius={140}
-          animate={{ duration: 400 }}
-          data={data.map(({ count }) => ({ x: '', y: count }))}
-          labels={() => ''}
-          colorScale={data.map(({ color }) => color)}
-        />
-        <p className="absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 font-medium text-lg">
-          {centerCount}
-        </p>
+    <>
+      <div
+        className={classNames(
+          'h-full inline-block align-bottom',
+          containerWidthClass
+        )}
+      >
+        <div className="w-full h-full relative">
+          <VictoryPie
+            padding={0}
+            innerRadius={175}
+            animate={{ duration: 400 }}
+            data={data.map(({ count }) => ({ x: '', y: count }))}
+            labels={() => ''}
+            colorScale={data.map(({ color }) => color)}
+          />
+          <p
+            className={classNames(
+              'absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 font-medium',
+              centerTitleClasses
+            )}
+          >
+            {centerCount <= MAX_COUNT ? centerCount : MAX_COUNT + '+'}
+          </p>
+        </div>
       </div>
-    </div>
+      {title && (
+        <p className="text-xs text-center font-semibold mt-2">{title}</p>
+      )}
+    </>
   );
 };
 
diff --git a/packages/extension/src/view/design-system/components/circlePieChart/stories/circlePieChart.stories.tsx b/packages/extension/src/view/design-system/components/circlePieChart/stories/circlePieChart.stories.tsx
index 2e9f3b8d..11417830 100644
--- a/packages/extension/src/view/design-system/components/circlePieChart/stories/circlePieChart.stories.tsx
+++ b/packages/extension/src/view/design-system/components/circlePieChart/stories/circlePieChart.stories.tsx
@@ -16,6 +16,7 @@
 /**
  * External dependencies.
  */
+import React from 'react';
 import type { Meta, StoryObj } from '@storybook/react';
 
 /**
@@ -34,7 +35,8 @@ export default meta;
 
 export const Primary: StoryObj<typeof meta> = {
   args: {
-    centerCount: 5,
+    centerCount: 39,
+    title: '1st party cookies',
     data: [
       {
         count: 5,
@@ -54,4 +56,69 @@ export const Primary: StoryObj<typeof meta> = {
       },
     ],
   },
+  render: (args) => (
+    <div className="w-16">
+      <CirclePieChart {...args} />
+    </div>
+  ),
+};
+
+export const Wide: StoryObj<typeof meta> = {
+  args: {
+    centerCount: 39,
+    title: '1st party cookies',
+    data: [
+      {
+        count: 5,
+        color: COLOR_MAP.functional,
+      },
+      {
+        count: 4,
+        color: COLOR_MAP.marketing,
+      },
+      {
+        count: 0,
+        color: COLOR_MAP.analytics,
+      },
+      {
+        count: 9,
+        color: COLOR_MAP.uncategorised,
+      },
+    ],
+  },
+  render: (args) => (
+    <div className="w-28 text-center">
+      <CirclePieChart {...args} />
+    </div>
+  ),
+};
+
+export const Secondary: StoryObj<typeof meta> = {
+  args: {
+    centerCount: 39,
+    isPrimary: false,
+    data: [
+      {
+        count: 5,
+        color: COLOR_MAP.functional,
+      },
+      {
+        count: 4,
+        color: COLOR_MAP.marketing,
+      },
+      {
+        count: 0,
+        color: COLOR_MAP.analytics,
+      },
+      {
+        count: 9,
+        color: COLOR_MAP.uncategorised,
+      },
+    ],
+  },
+  render: (args) => (
+    <div className="w-8 text-center">
+      <CirclePieChart {...args} />
+    </div>
+  ),
 };
diff --git a/packages/extension/src/view/design-system/components/circlePieChart/stories/emptyCirclePieChart.stories.tsx b/packages/extension/src/view/design-system/components/circlePieChart/stories/emptyCirclePieChart.stories.tsx
new file mode 100644
index 00000000..8b22e8f7
--- /dev/null
+++ b/packages/extension/src/view/design-system/components/circlePieChart/stories/emptyCirclePieChart.stories.tsx
@@ -0,0 +1,66 @@
+/*
+ * Copyright 2023 Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+/**
+ * External dependencies.
+ */
+import React from 'react';
+import type { Meta, StoryObj } from '@storybook/react';
+
+/**
+ * Internal dependencies.
+ */
+import EmptyCirclePieChart from '../emptyCirclePieChart';
+
+const meta: Meta<typeof EmptyCirclePieChart> = {
+  title: 'Extension/DesignSystem/CirclePieChart/EmptyCirclePieChart',
+  component: EmptyCirclePieChart,
+  tags: ['autodocs'],
+};
+
+export default meta;
+
+export const Primary: StoryObj<typeof meta> = {
+  render: (args) => (
+    <div className="w-16 text-center">
+      <EmptyCirclePieChart {...args} />
+    </div>
+  ),
+  args: {
+    title: '3rd party cookies',
+  },
+};
+
+export const Wide: StoryObj<typeof meta> = {
+  render: (args) => (
+    <div className="w-28 text-center">
+      <EmptyCirclePieChart {...args} />
+    </div>
+  ),
+  args: {
+    title: '3rd party cookies',
+  },
+};
+
+export const Secondary: StoryObj<typeof meta> = {
+  render: (args) => (
+    <div className="w-8 text-center">
+      <EmptyCirclePieChart {...args} />
+    </div>
+  ),
+  args: {
+    isPrimary: false,
+  },
+};
diff --git a/packages/extension/src/view/design-system/theme/colors.ts b/packages/extension/src/view/design-system/theme/colors.ts
index 3fbf1335..0f068b4f 100644
--- a/packages/extension/src/view/design-system/theme/colors.ts
+++ b/packages/extension/src/view/design-system/theme/colors.ts
@@ -15,8 +15,10 @@
  */
 
 export const COLOR_MAP = {
-  functional: '#00B04C',
-  marketing: '#FFBF00',
-  analytics: '#3D7AFC',
-  uncategorised: '#FC260A',
+  functional: '#5CC971',
+  marketing: '#F3AE4E',
+  analytics: '#4C79F4',
+  uncategorised: '#EC7159',
+  brightGray: '#EBEBEB',
+  mediumGray: '#BDBDBD',
 };
diff --git a/packages/extension/src/view/devtools/components/tabContent/cookies/components/index.ts b/packages/extension/src/view/devtools/components/tabContent/cookies/cookiesLanding/index.tsx
similarity index 77%
rename from packages/extension/src/view/devtools/components/tabContent/cookies/components/index.ts
rename to packages/extension/src/view/devtools/components/tabContent/cookies/cookiesLanding/index.tsx
index 0d99b222..ec6a1f58 100644
--- a/packages/extension/src/view/devtools/components/tabContent/cookies/components/index.ts
+++ b/packages/extension/src/view/devtools/components/tabContent/cookies/cookiesLanding/index.tsx
@@ -13,6 +13,13 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
+/**
+ * External dependencies.
+ */
+import React from 'react';
+
+const CookiesLanding = () => {
+  return <p>Landing page placeholder</p>;
+};
 
-export { default as CookieDetails } from './cookieDetails';
-export { default as CookieTable } from './cookieTable';
+export default CookiesLanding;
diff --git a/packages/extension/src/view/devtools/components/tabContent/cookies/components/cookieDetails/details.tsx b/packages/extension/src/view/devtools/components/tabContent/cookies/cookiesListing/cookieDetails/details.tsx
similarity index 100%
rename from packages/extension/src/view/devtools/components/tabContent/cookies/components/cookieDetails/details.tsx
rename to packages/extension/src/view/devtools/components/tabContent/cookies/cookiesListing/cookieDetails/details.tsx
diff --git a/packages/extension/src/view/devtools/components/tabContent/cookies/components/cookieDetails/index.tsx b/packages/extension/src/view/devtools/components/tabContent/cookies/cookiesListing/cookieDetails/index.tsx
similarity index 100%
rename from packages/extension/src/view/devtools/components/tabContent/cookies/components/cookieDetails/index.tsx
rename to packages/extension/src/view/devtools/components/tabContent/cookies/cookiesListing/cookieDetails/index.tsx
diff --git a/packages/extension/src/view/devtools/components/tabContent/cookies/components/cookieDetails/stories/cookieDetails.stories.ts b/packages/extension/src/view/devtools/components/tabContent/cookies/cookiesListing/cookieDetails/stories/cookieDetails.stories.ts
similarity index 96%
rename from packages/extension/src/view/devtools/components/tabContent/cookies/components/cookieDetails/stories/cookieDetails.stories.ts
rename to packages/extension/src/view/devtools/components/tabContent/cookies/cookiesListing/cookieDetails/stories/cookieDetails.stories.ts
index 267bd347..2c0a0afb 100644
--- a/packages/extension/src/view/devtools/components/tabContent/cookies/components/cookieDetails/stories/cookieDetails.stories.ts
+++ b/packages/extension/src/view/devtools/components/tabContent/cookies/cookiesListing/cookieDetails/stories/cookieDetails.stories.ts
@@ -22,7 +22,7 @@ import type { Meta, StoryObj } from '@storybook/react';
  * Internal dependencies.
  */
 
-import { CookieDetails } from '../..';
+import CookieDetails from '..';
 
 const meta = {
   title: 'Extension/DevTools/CookiesPanel/CookieDetails',
diff --git a/packages/extension/src/view/devtools/components/tabContent/cookies/components/cookieDetails/stories/details.stories.ts b/packages/extension/src/view/devtools/components/tabContent/cookies/cookiesListing/cookieDetails/stories/details.stories.ts
similarity index 100%
rename from packages/extension/src/view/devtools/components/tabContent/cookies/components/cookieDetails/stories/details.stories.ts
rename to packages/extension/src/view/devtools/components/tabContent/cookies/cookiesListing/cookieDetails/stories/details.stories.ts
diff --git a/packages/extension/src/view/devtools/components/tabContent/cookies/components/cookieTable/index.tsx b/packages/extension/src/view/devtools/components/tabContent/cookies/cookiesListing/cookieTable/index.tsx
similarity index 100%
rename from packages/extension/src/view/devtools/components/tabContent/cookies/components/cookieTable/index.tsx
rename to packages/extension/src/view/devtools/components/tabContent/cookies/cookiesListing/cookieTable/index.tsx
diff --git a/packages/extension/src/view/devtools/components/tabContent/cookies/components/cookieTable/stories/cookieTable.stories.ts b/packages/extension/src/view/devtools/components/tabContent/cookies/cookiesListing/cookieTable/stories/cookieTable.stories.ts
similarity index 96%
rename from packages/extension/src/view/devtools/components/tabContent/cookies/components/cookieTable/stories/cookieTable.stories.ts
rename to packages/extension/src/view/devtools/components/tabContent/cookies/cookiesListing/cookieTable/stories/cookieTable.stories.ts
index 0de42e16..2796498c 100644
--- a/packages/extension/src/view/devtools/components/tabContent/cookies/components/cookieTable/stories/cookieTable.stories.ts
+++ b/packages/extension/src/view/devtools/components/tabContent/cookies/cookiesListing/cookieTable/stories/cookieTable.stories.ts
@@ -23,7 +23,7 @@ import type { Meta, StoryObj } from '@storybook/react';
  * Internal dependencies.
  */
 import { TempCookieData } from './tempData';
-import { CookieTable } from '../..';
+import CookieTable from '..';
 
 const meta = {
   title: 'Extension/DevTools/CookiesPanel/CookieTable',
diff --git a/packages/extension/src/view/devtools/components/tabContent/cookies/components/cookieTable/stories/tempData.ts b/packages/extension/src/view/devtools/components/tabContent/cookies/cookiesListing/cookieTable/stories/tempData.ts
similarity index 100%
rename from packages/extension/src/view/devtools/components/tabContent/cookies/components/cookieTable/stories/tempData.ts
rename to packages/extension/src/view/devtools/components/tabContent/cookies/cookiesListing/cookieTable/stories/tempData.ts
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
new file mode 100644
index 00000000..699d178c
--- /dev/null
+++ b/packages/extension/src/view/devtools/components/tabContent/cookies/cookiesListing/index.tsx
@@ -0,0 +1,78 @@
+/*
+ * Copyright 2023 Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+/**
+ * External dependencies.
+ */
+import React, { useMemo } from 'react';
+import { Resizable } from 're-resizable';
+
+/**
+ * Internal dependencies.
+ */
+import {
+  type CookieTableData,
+  useCookieStore,
+} from '../../../../stateProviders/syncCookieStore';
+import CookieDetails from './cookieDetails';
+import CookieTable from './cookieTable';
+
+const CookiesListing = () => {
+  const { cookies, selectedFrame, tabFrames } = useCookieStore(({ state }) => ({
+    cookies: state.tabCookies,
+    selectedFrame: state.selectedFrame,
+    tabFrames: state.tabFrames,
+  }));
+
+  const calculatedCookies = useMemo(() => {
+    const frameFilteredCookies: { [key: string]: CookieTableData } = {};
+    if (cookies && selectedFrame && tabFrames && tabFrames[selectedFrame]) {
+      Object.entries(cookies).forEach(([key, cookie]) => {
+        tabFrames[selectedFrame].frameIds?.forEach((frameId) => {
+          if (cookie.frameIdList?.includes(frameId)) {
+            frameFilteredCookies[key] = cookie;
+          }
+        });
+      });
+    }
+    return Object.values(frameFilteredCookies);
+  }, [cookies, selectedFrame, tabFrames]);
+
+  return (
+    <div className="h-full flex flex-col">
+      <Resizable
+        defaultSize={{
+          width: '100%',
+          height: '80%',
+        }}
+        minHeight="6%"
+        maxHeight="98%"
+        enable={{
+          top: false,
+          right: false,
+          bottom: true,
+          left: false,
+        }}
+      >
+        <CookieTable cookies={calculatedCookies} />
+      </Resizable>
+      <div className="w-full h-full bg-white border-2 border-gray-300 shadow overflow-auto">
+        <CookieDetails />
+      </div>
+    </div>
+  );
+};
+
+export default CookiesListing;
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 f6f6b1ac..4cdf863d 100644
--- a/packages/extension/src/view/devtools/components/tabContent/cookies/index.tsx
+++ b/packages/extension/src/view/devtools/components/tabContent/cookies/index.tsx
@@ -17,39 +17,20 @@
 /**
  * External dependencies.
  */
-import React, { useMemo } from 'react';
-import { Resizable } from 're-resizable';
+import React from 'react';
 
 /**
  * Internal dependencies.
  */
-import {
-  useCookieStore,
-  type CookieTableData,
-} from '../../../stateProviders/syncCookieStore';
-import { CookieDetails, CookieTable } from './components';
+import { useCookieStore } from '../../../stateProviders/syncCookieStore';
+import CookiesListing from './cookiesListing';
+import CookiesLanding from './cookiesLanding';
 
 const Cookies = () => {
-  const { cookies, selectedFrame, tabFrames } = useCookieStore(({ state }) => ({
-    cookies: state.tabCookies,
+  const { selectedFrame } = useCookieStore(({ state }) => ({
     selectedFrame: state.selectedFrame,
-    tabFrames: state.tabFrames,
   }));
 
-  const calculatedCookies = useMemo(() => {
-    const frameFilteredCookies: { [key: string]: CookieTableData } = {};
-    if (cookies && selectedFrame && tabFrames && tabFrames[selectedFrame]) {
-      Object.entries(cookies).forEach(([key, cookie]) => {
-        tabFrames[selectedFrame].frameIds?.forEach((frameId) => {
-          if (cookie.frameIdList?.includes(frameId)) {
-            frameFilteredCookies[key] = cookie;
-          }
-        });
-      });
-    }
-    return Object.values(frameFilteredCookies);
-  }, [cookies, selectedFrame, tabFrames]);
-
   return (
     <div
       className={`h-full border border-american-silver ${
@@ -57,31 +38,7 @@ const Cookies = () => {
       }`}
       data-testid="cookies-content"
     >
-      {selectedFrame ? (
-        <div className="h-full flex flex-col">
-          <Resizable
-            defaultSize={{
-              width: '100%',
-              height: '80%',
-            }}
-            minHeight="6%"
-            maxHeight="98%"
-            enable={{
-              top: false,
-              right: false,
-              bottom: true,
-              left: false,
-            }}
-          >
-            <CookieTable cookies={calculatedCookies} />
-          </Resizable>
-          <div className="w-full h-full bg-white border-2 border-gray-300 shadow overflow-auto">
-            <CookieDetails />
-          </div>
-        </div>
-      ) : (
-        <p> landing page placeholder</p>
-      )}
+      {selectedFrame ? <CookiesListing /> : <CookiesLanding />}
     </div>
   );
 };
diff --git a/packages/extension/src/view/popup/app.tsx b/packages/extension/src/view/popup/app.tsx
index 75e0d8e4..df76cf2c 100644
--- a/packages/extension/src/view/popup/app.tsx
+++ b/packages/extension/src/view/popup/app.tsx
@@ -121,40 +121,24 @@ const App: React.FC = () => {
 
   return (
     <div className="w-96 h-80 flex justify-center items-center flex-col">
-      <div className="w-full flex-1 flex gap-16 pt-6 px-12">
+      <div className="w-full flex-1 flex gap-16 px-12">
         <div className="w-full h-full flex flex-col justify-center items-center">
-          <div className="flex-1 w-full">
-            {cookieStats.firstParty.total ? (
-              <CirclePieChart
-                centerCount={cookieStats.firstParty.total}
-                data={firstPartyPiechartData}
-              />
-            ) : (
-              <div className="w-full h-full flex justify-center items-center">
-                <h1 className="text-center">First Party Cookies Not Found</h1>
-              </div>
-            )}
+          <div className="w-full text-center">
+            <CirclePieChart
+              centerCount={cookieStats.firstParty.total}
+              data={firstPartyPiechartData}
+              title="1st Party Cookies"
+            />
           </div>
-          {cookieStats.firstParty.total ? (
-            <p className="font-bold text-xs">1st Party Cookies</p>
-          ) : null}
         </div>
         <div className="w-full h-full flex flex-col justify-center items-center">
-          <div className="flex-1 w-full h-full">
-            {cookieStats.thirdParty.total ? (
-              <CirclePieChart
-                centerCount={cookieStats.thirdParty.total}
-                data={thirdPartyPiechartData}
-              />
-            ) : (
-              <div className="w-full h-full flex justify-center items-center">
-                <h1 className="text-center">Third Party Cookies Not Found</h1>
-              </div>
-            )}
+          <div className="w-full text-center">
+            <CirclePieChart
+              centerCount={cookieStats.thirdParty.total}
+              data={thirdPartyPiechartData}
+              title="3rd Party Cookies"
+            />
           </div>
-          {cookieStats.thirdParty.total ? (
-            <p className="font-bold text-xs">3rd Party Cookies</p>
-          ) : null}
         </div>
       </div>
       <div className="mt-3">
diff --git a/packages/extension/src/view/popup/components/legend/index.tsx b/packages/extension/src/view/popup/components/legend/index.tsx
index 65b8cd95..c27f3f33 100644
--- a/packages/extension/src/view/popup/components/legend/index.tsx
+++ b/packages/extension/src/view/popup/components/legend/index.tsx
@@ -33,7 +33,7 @@ const Legend = ({ legendItemList }: LegendProps) => {
       {legendItemList.map(({ label, count, color }, index) => (
         <div key={index} className="w-36 flex items-center justify-center my-1">
           <Circle color={color} />
-          <p className="flex-1 text-chart-label text-xs">{label}</p>
+          <p className="flex-1 text-chart-label text-xs ml-2">{label}</p>
           <p className="text-chart-label text-xs">{count}</p>
         </div>
       ))}
diff --git a/packages/extension/tailwind.config.cjs b/packages/extension/tailwind.config.cjs
index 48af0b0c..73e71ee3 100644
--- a/packages/extension/tailwind.config.cjs
+++ b/packages/extension/tailwind.config.cjs
@@ -31,6 +31,10 @@ module.exports = {
     fontFamily: {
       ...defaultTheme.fontFamily,
     },
+    fontSize: {
+      ...defaultTheme.fontSize,
+      xxxs: '0.625rem', // 10px - Only for edge cases
+    },
     textColor: {
       ...colors,
       primary: '#000',
@@ -41,6 +45,7 @@ module.exports = {
       'chart-label': '#111B21',
       'granite-gray': '#5F6369',
       'outer-space': '#303942',
+      'raisin-black': '#212121',
     },
     backgroundColor: {
       ...colors,


Test patch:
diff --git a/packages/extension/src/view/design-system/components/circle/tests/circle.tsx b/packages/extension/src/view/design-system/components/circle/tests/circle.tsx
index d828687b..62dea456 100644
--- a/packages/extension/src/view/design-system/components/circle/tests/circle.tsx
+++ b/packages/extension/src/view/design-system/components/circle/tests/circle.tsx
@@ -28,7 +28,7 @@ describe('Circle', () => {
     const { container } = render(<Circle color={COLOR_MAP.functional} />);
 
     // Check if the Circle div has the correct background color style
-    const circleDiv = container.querySelector('.rounded-full');
+    const circleDiv = container.querySelector('div');
     expect(circleDiv).toHaveStyle(`background-color: ${COLOR_MAP.functional}`);
   });
 });
diff --git a/packages/extension/src/view/design-system/components/circle/tests/circleEmpty.tsx b/packages/extension/src/view/design-system/components/circle/tests/circleEmpty.tsx
new file mode 100644
index 00000000..fb869546
--- /dev/null
+++ b/packages/extension/src/view/design-system/components/circle/tests/circleEmpty.tsx
@@ -0,0 +1,38 @@
+/*
+ * Copyright 2023 Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+/**
+ * External dependencies.
+ */
+import React from 'react';
+import { render } from '@testing-library/react';
+import '@testing-library/jest-dom/extend-expect';
+
+/**
+ * Internal dependencies.
+ */
+import CircleEmpty from '../circleEmpty';
+
+describe('CircleEmpty', () => {
+  const testColor = 'red';
+
+  it('renders the CircleEmpty with the correct background color', () => {
+    const { container } = render(<CircleEmpty color={testColor} />);
+
+    // Check if the CircleEmpty div has the correct background color style
+    const circleEmptyDiv = container.querySelector('div');
+    expect(circleEmptyDiv).toHaveStyle(`background-color: ${testColor}`);
+  });
+});
diff --git a/packages/extension/src/view/design-system/components/circlePieChart/tests/circlePieChart.tsx b/packages/extension/src/view/design-system/components/circlePieChart/tests/circlePieChart.tsx
index c87df7e8..ce155823 100644
--- a/packages/extension/src/view/design-system/components/circlePieChart/tests/circlePieChart.tsx
+++ b/packages/extension/src/view/design-system/components/circlePieChart/tests/circlePieChart.tsx
@@ -20,34 +20,130 @@ import '@testing-library/jest-dom/extend-expect';
 /**
  * Internal dependencies.
  */
-import CirclePieChart from '..';
+import CirclePieChart, { MAX_COUNT } from '..';
 
 describe('CirclePieChart', () => {
   const testData = [
-    { count: 25, color: 'red' },
-    { count: 50, color: 'blue' },
-    { count: 75, color: 'green' },
+    { count: 50, color: 'red' },
+    { count: 73, color: 'blue' },
   ];
-  const centerCount = 150;
 
-  it('renders the VictoryPie chart with the correct data', () => {
-    const { container } = render(
-      <CirclePieChart centerCount={centerCount} data={testData} />
+  it('renders EmptyCirclePieChart when centerCount is 0', () => {
+    const centerCount = 0;
+    const title = 'Empty Chart';
+    const fallbackText = 'No Data';
+
+    const { getByText } = render(
+      <CirclePieChart
+        centerCount={centerCount}
+        data={testData}
+        title={title}
+        fallbackText={fallbackText}
+      />
+    );
+
+    // Check if the EmptyCirclePieChart is rendered when centerCount is 0
+    const emptyChartTitle = getByText(title);
+    expect(emptyChartTitle).toBeInTheDocument();
+    const fallbackTextElement = getByText(fallbackText);
+    expect(fallbackTextElement).toBeInTheDocument();
+  });
+
+  it('renders CirclePieChart with correct centerCount when centerCount is less than or equal to MAX_COUNT', () => {
+    const centerCount = 123;
+    const title = '1st Party cookies';
+
+    const { getByText, container } = render(
+      <CirclePieChart centerCount={centerCount} data={testData} title={title} />
     );
 
-    // Check if the VictoryPie is rendered with the correct data
-    const paths = container.querySelectorAll('path[role="presentation"]');
-    expect(paths.length).toBe(testData.length);
+    // Check if the CirclePieChart is rendered with the correct centerCount
+    const centerCountText = getByText(centerCount.toString());
+    expect(centerCountText).toBeInTheDocument();
+
+    // Check if the VictoryPie is rendered with the correct data points
+    const slices = container.querySelectorAll('path[role="presentation"]');
+    expect(slices.length).toBe(testData.length);
   });
 
-  it('renders the centerCount text', () => {
+  it('renders CirclePieChart with MAX_COUNT+ when centerCount exceeds MAX_COUNT', () => {
+    const centerCount = MAX_COUNT + 100; // Exceeds MAX_COUNT (999)
+    const title = '3rd party cookies';
+
     const { getByText } = render(
+      <CirclePieChart centerCount={centerCount} data={testData} title={title} />
+    );
+
+    // Check if the CirclePieChart displays MAX_COUNT+ when centerCount exceeds MAX_COUNT
+    const maxCountText = `${MAX_COUNT}+`;
+    const centerCountText = getByText(maxCountText);
+    expect(centerCountText).toBeInTheDocument();
+  });
+
+  it('renders CirclePieChart with the provided title', () => {
+    const centerCount = 80;
+    const title = 'Total cookies';
+
+    const { getByText } = render(
+      <CirclePieChart centerCount={centerCount} data={testData} title={title} />
+    );
+
+    // Check if the CirclePieChart renders the provided title
+    const chartTitle = getByText(title);
+    expect(chartTitle).toBeInTheDocument();
+  });
+
+  it('renders CirclePieChart with default primary container width class', () => {
+    const centerCount = 50;
+
+    const { container } = render(
       <CirclePieChart centerCount={centerCount} data={testData} />
     );
 
-    // Check if the centerCount text is rendered
-    const text: string = centerCount.toString();
-    const centerCountElement = getByText(text);
-    expect(centerCountElement).toBeInTheDocument();
+    // Check if the CirclePieChart renders with the default primary container width class
+    const primaryContainer = container.querySelector('.w-16');
+    expect(primaryContainer).toBeInTheDocument();
+  });
+
+  it('renders CirclePieChart with custom container width class when isPrimary is false', () => {
+    const centerCount = 60;
+
+    const { container } = render(
+      <CirclePieChart
+        centerCount={centerCount}
+        data={testData}
+        isPrimary={false}
+      />
+    );
+
+    // Check if the CirclePieChart renders with the custom container width class for non-primary charts
+    const secondaryContainer = container.querySelector('.w-8');
+    expect(secondaryContainer).toBeInTheDocument();
+  });
+
+  it('renders CirclePieChart with the correct font size class for primary and non-primary charts', () => {
+    const primaryCenterCount = 100;
+    const secondaryCenterCount = MAX_COUNT + 1; // Exceeds MAX_COUNT (999)
+
+    const { getByText, rerender } = render(
+      <CirclePieChart centerCount={primaryCenterCount} data={testData} />
+    );
+
+    // Check if the CirclePieChart renders with the correct font size class for primary charts
+    const primaryFontSizeClass = getByText(primaryCenterCount.toString());
+    expect(primaryFontSizeClass).toHaveClass('text-2xl');
+
+    // Rerender with non-primary centerCount
+    rerender(
+      <CirclePieChart
+        centerCount={secondaryCenterCount}
+        data={testData}
+        isPrimary={false}
+      />
+    );
+
+    // Check if the CirclePieChart renders with the correct font size class for non-primary charts
+    const nonPrimaryFontSizeClass = getByText(MAX_COUNT + '+');
+    expect(nonPrimaryFontSizeClass).toHaveClass('text-xxxs');
   });
 });
diff --git a/packages/extension/src/view/design-system/components/circlePieChart/tests/emptyCirclePieChart.tsx b/packages/extension/src/view/design-system/components/circlePieChart/tests/emptyCirclePieChart.tsx
new file mode 100644
index 00000000..9760fb26
--- /dev/null
+++ b/packages/extension/src/view/design-system/components/circlePieChart/tests/emptyCirclePieChart.tsx
@@ -0,0 +1,50 @@
+/*
+ * Copyright 2023 Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+/**
+ * External dependencies.
+ */
+import React from 'react';
+import { render } from '@testing-library/react';
+import '@testing-library/jest-dom/extend-expect';
+
+/**
+ * Internal Dependencies
+ */
+import EmptyCirclePieChart from '../emptyCirclePieChart';
+
+describe('EmptyCirclePieChart', () => {
+  it('renders the fallback text correctly when provided', () => {
+    const fallbackText = 'No data';
+
+    const { getByText } = render(
+      <EmptyCirclePieChart fallbackText={fallbackText} />
+    );
+
+    // Check if the fallback text is rendered correctly when provided
+    const fallbackTextElement = getByText(fallbackText);
+    expect(fallbackTextElement).toBeInTheDocument();
+  });
+
+  it('renders the default fallback text correctly when not provided', () => {
+    const defaultFallbackText = 'Not Found';
+
+    const { getByText } = render(<EmptyCirclePieChart />);
+
+    // Check if the default fallback text is rendered correctly when not provided
+    const defaultFallbackTextElement = getByText(defaultFallbackText);
+    expect(defaultFallbackTextElement).toBeInTheDocument();
+  });
+});
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 5a27a600..c7054349 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
@@ -28,7 +28,7 @@ import SinonChrome from 'sinon-chrome';
 import CookieTab from '..';
 import type { CookieStoreContext } from '../../../../stateProviders/syncCookieStore';
 import { emptyAnalytics } from '../../../../../../worker/findAnalyticsMatch';
-import { CookieDetails } from '../components';
+import CookieDetails from '../cookiesListing/cookieDetails';
 import { useContentPanelStore } from '../../../../stateProviders/contentPanelStore';
 
 const emptyCookie = {