Repository: gitify-app/gitify
Pull request #1222 (2024-06-11 11:55:30)

refactor: new pill component
thanks for the nudge @bmulholland 


Patch:
diff --git a/.github/workflows/build-app.yml b/.github/workflows/build.yml
similarity index 100%
rename from .github/workflows/build-app.yml
rename to .github/workflows/build.yml
diff --git a/.github/workflows/run-tests.yml b/.github/workflows/test.yml
similarity index 100%
rename from .github/workflows/run-tests.yml
rename to .github/workflows/test.yml
diff --git a/.github/workflows/validator.yml b/.github/workflows/validator.yml
index 780ee883..725d5731 100644
--- a/.github/workflows/validator.yml
+++ b/.github/workflows/validator.yml
@@ -6,7 +6,7 @@ on:
 
 jobs:
   pr-lint:
-    name: Validate PR name
+    name: Validate PR title
     runs-on: ubuntu-latest
     steps:
       - name: Validate PR title
diff --git a/README.md b/README.md
index 1198e802..e48c13ab 100644
--- a/README.md
+++ b/README.md
@@ -95,7 +95,7 @@ For more information, see [LICENSE](LICENSE).
 [biome-website]: biomejs.dev/
 [coveralls]: https://coveralls.io/github/gitify-app/gitify
 [coveralls-badge]: https://img.shields.io/coverallsCoverage/github/gitify-app/gitify
-[build-workflow-badge]: https://github.com/gitify-app/gitify/actions/workflows/build-app.yml/badge.svg
+[build-workflow-badge]: https://github.com/gitify-app/gitify/actions/workflows/build.yml/badge.svg
 [release-workflow-badge]: https://github.com/gitify-app/gitify/actions/workflows/release.yml/badge.svg
 [downloads-total-badge]: https://img.shields.io/github/downloads/gitify-app/gitify/total?label=downloads@all
 [downloads-latest-badge]: https://img.shields.io/github/downloads/gitify-app/gitify/latest/total
diff --git a/src/components/NotificationRow.tsx b/src/components/NotificationRow.tsx
index 909eba3f..b4ad71a0 100644
--- a/src/components/NotificationRow.tsx
+++ b/src/components/NotificationRow.tsx
@@ -11,7 +11,6 @@ import {
 import { type FC, type MouseEvent, useCallback, useContext } from 'react';
 
 import { AppContext } from '../context/App';
-import { PILL_CLASS_NAME } from '../styles/gitify';
 import { type Account, IconColor } from '../types';
 import type { Notification } from '../typesGitHub';
 import {
@@ -25,6 +24,7 @@ import {
 } from '../utils/icons';
 import { openNotification, openUserProfile } from '../utils/links';
 import { formatReason } from '../utils/reason';
+import { PillButton } from './buttons/PillButton';
 
 interface IProps {
   account: Account;
@@ -143,17 +143,14 @@ export const NotificationRow: FC<IProps> = ({ notification, account }) => {
           {settings.showPills && (
             <div>
               {notification.subject?.linkedIssues?.length > 0 && (
-                <span title={linkedIssuesPillDescription}>
-                  <button type="button" className={PILL_CLASS_NAME}>
-                    <IssueClosedIcon
-                      size={12}
-                      className={`mr-1 ${IconColor.GREEN}`}
-                      aria-label={linkedIssuesPillDescription}
-                    />
-                    {notification.subject.linkedIssues.length}
-                  </button>
-                </span>
+                <PillButton
+                  title={linkedIssuesPillDescription}
+                  metric={notification.subject.linkedIssues.length}
+                  icon={IssueClosedIcon}
+                  color={IconColor.GREEN}
+                />
               )}
+
               {notification.subject.reviews?.map((review) => {
                 const icon = getPullRequestReviewIcon(review);
                 if (!icon) {
@@ -161,59 +158,41 @@ export const NotificationRow: FC<IProps> = ({ notification, account }) => {
                 }
 
                 return (
-                  <span key={review.state} title={icon.description}>
-                    <button type="button" className={PILL_CLASS_NAME}>
-                      <icon.type
-                        size={12}
-                        className={`mr-1 ${icon.color}`}
-                        aria-label={icon.description}
-                      />
-                      {review.users.length}
-                    </button>
-                  </span>
+                  <PillButton
+                    key={review.state}
+                    title={icon.description}
+                    metric={review.users.length}
+                    icon={icon.type}
+                    color={icon.color}
+                  />
                 );
               })}
               {notification.subject?.comments > 0 && (
-                <span title={commentsPillDescription}>
-                  <button type="button" className={PILL_CLASS_NAME}>
-                    <CommentIcon
-                      size={12}
-                      className={`mr-1 ${IconColor.GRAY}`}
-                      aria-label={commentsPillDescription}
-                    />
-                    {notification.subject.comments}
-                  </button>
-                </span>
+                <PillButton
+                  title={commentsPillDescription}
+                  metric={notification.subject.comments}
+                  icon={CommentIcon}
+                  color={IconColor.GRAY}
+                />
               )}
               {notification.subject?.labels?.length > 0 && (
-                <span title={labelsPillDescription}>
-                  <button type="button" className={PILL_CLASS_NAME}>
-                    <TagIcon
-                      size={12}
-                      className={`mr-1 ${IconColor.GRAY}`}
-                      aria-label={labelsPillDescription}
-                    />
-                    {notification.subject.labels.length}
-                  </button>
-                </span>
+                <PillButton
+                  title={labelsPillDescription}
+                  metric={notification.subject.labels.length}
+                  icon={TagIcon}
+                  color={IconColor.GRAY}
+                />
               )}
               {notification.subject.milestone && (
-                <span
-                  className="ml-1"
+                <PillButton
                   title={notification.subject.milestone.title}
-                >
-                  <button type="button" className={PILL_CLASS_NAME}>
-                    <MilestoneIcon
-                      size={12}
-                      className={
-                        notification.subject.milestone.state === 'open'
-                          ? IconColor.GREEN
-                          : IconColor.RED
-                      }
-                      aria-label={notification.subject.milestone.title}
-                    />
-                  </button>
-                </span>
+                  icon={MilestoneIcon}
+                  color={
+                    notification.subject.milestone.state === 'open'
+                      ? IconColor.GREEN
+                      : IconColor.RED
+                  }
+                />
               )}
             </div>
           )}
diff --git a/src/components/buttons/PillButton.tsx b/src/components/buttons/PillButton.tsx
new file mode 100644
index 00000000..5f92b3d1
--- /dev/null
+++ b/src/components/buttons/PillButton.tsx
@@ -0,0 +1,29 @@
+import type { Icon } from '@primer/octicons-react';
+import type { FC } from 'react';
+import type { IconColor } from '../../types';
+
+export interface IPillButton {
+  key?: string;
+  title: string;
+  metric?: number;
+  icon: Icon;
+  color: IconColor;
+}
+
+export const PillButton: FC<IPillButton> = (props: IPillButton) => {
+  return (
+    <span title={props.title}>
+      <button
+        type="button"
+        className="rounded-full text-xss px-1 m-0.5 bg-gray-100 hover:bg-gray-200 dark:bg-gray-800 dark:hover:bg-gray-700"
+      >
+        <props.icon
+          size={12}
+          className={`mr-1 ${props.color}`}
+          aria-label={props.title}
+        />
+        {props.metric}
+      </button>
+    </span>
+  );
+};
diff --git a/src/styles/gitify.ts b/src/styles/gitify.ts
index f33ad022..22be1026 100644
--- a/src/styles/gitify.ts
+++ b/src/styles/gitify.ts
@@ -4,7 +4,4 @@ export const BUTTON_CLASS_NAME =
 export const BUTTON_SIDEBAR_CLASS_NAME =
   'flex justify-evenly items-center bg-transparent border-0 w-full text-sm text-white my-1 py-2 cursor-pointer hover:text-gray-500 focus:outline-none disabled:text-gray-500 disabled:cursor-default';
 
-export const PILL_CLASS_NAME =
-  'rounded-full text-xss px-1 m-0.5 bg-gray-100 hover:bg-gray-200 dark:bg-gray-800 dark:hover:bg-gray-700';
-
 export const READ_NOTIFICATION_CLASS_NAME = 'opacity-50 dark:opacity-50';


Test patch:
diff --git a/src/components/__snapshots__/NotificationRow.test.tsx.snap b/src/components/__snapshots__/NotificationRow.test.tsx.snap
index 2a20cdc1..270ecb25 100644
--- a/src/components/__snapshots__/NotificationRow.test.tsx.snap
+++ b/src/components/__snapshots__/NotificationRow.test.tsx.snap
@@ -3377,7 +3377,6 @@ exports[`components/NotificationRow.tsx notification pills / metrics milestone p
                 </button>
               </span>
               <span
-                class="ml-1"
                 title="Milestone 1"
               >
                 <button
@@ -3386,7 +3385,7 @@ exports[`components/NotificationRow.tsx notification pills / metrics milestone p
                 >
                   <svg
                     aria-label="Milestone 1"
-                    class="text-red-500"
+                    class="mr-1 text-red-500"
                     fill="currentColor"
                     focusable="false"
                     height="12"
@@ -3673,7 +3672,6 @@ exports[`components/NotificationRow.tsx notification pills / metrics milestone p
               </button>
             </span>
             <span
-              class="ml-1"
               title="Milestone 1"
             >
               <button
@@ -3682,7 +3680,7 @@ exports[`components/NotificationRow.tsx notification pills / metrics milestone p
               >
                 <svg
                   aria-label="Milestone 1"
-                  class="text-red-500"
+                  class="mr-1 text-red-500"
                   fill="currentColor"
                   focusable="false"
                   height="12"
@@ -4026,7 +4024,6 @@ exports[`components/NotificationRow.tsx notification pills / metrics milestone p
                 </button>
               </span>
               <span
-                class="ml-1"
                 title="Milestone 1"
               >
                 <button
@@ -4035,7 +4032,7 @@ exports[`components/NotificationRow.tsx notification pills / metrics milestone p
                 >
                   <svg
                     aria-label="Milestone 1"
-                    class="text-green-500"
+                    class="mr-1 text-green-500"
                     fill="currentColor"
                     focusable="false"
                     height="12"
@@ -4322,7 +4319,6 @@ exports[`components/NotificationRow.tsx notification pills / metrics milestone p
               </button>
             </span>
             <span
-              class="ml-1"
               title="Milestone 1"
             >
               <button
@@ -4331,7 +4327,7 @@ exports[`components/NotificationRow.tsx notification pills / metrics milestone p
               >
                 <svg
                   aria-label="Milestone 1"
-                  class="text-green-500"
+                  class="mr-1 text-green-500"
                   fill="currentColor"
                   focusable="false"
                   height="12"
diff --git a/src/components/buttons/PillButton.test.tsx b/src/components/buttons/PillButton.test.tsx
new file mode 100644
index 00000000..4c8c62e5
--- /dev/null
+++ b/src/components/buttons/PillButton.test.tsx
@@ -0,0 +1,17 @@
+import { MarkGithubIcon } from '@primer/octicons-react';
+import { render } from '@testing-library/react';
+import { IconColor } from '../../types';
+import { type IPillButton, PillButton } from './PillButton';
+
+describe('components/buttons/PillButton.tsx', () => {
+  it('should render', () => {
+    const props: IPillButton = {
+      title: 'Mock Pill',
+      metric: 1,
+      icon: MarkGithubIcon,
+      color: IconColor.GREEN,
+    };
+    const tree = render(<PillButton {...props} />);
+    expect(tree).toMatchSnapshot();
+  });
+});
diff --git a/src/components/buttons/__snapshots__/PillButton.test.tsx.snap b/src/components/buttons/__snapshots__/PillButton.test.tsx.snap
new file mode 100644
index 00000000..000c3b99
--- /dev/null
+++ b/src/components/buttons/__snapshots__/PillButton.test.tsx.snap
@@ -0,0 +1,114 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`components/buttons/PillButton.tsx should render 1`] = `
+{
+  "asFragment": [Function],
+  "baseElement": <body>
+    <div>
+      <span
+        title="Mock Pill"
+      >
+        <button
+          class="rounded-full text-xss px-1 m-0.5 bg-gray-100 hover:bg-gray-200 dark:bg-gray-800 dark:hover:bg-gray-700"
+          type="button"
+        >
+          <svg
+            aria-label="Mock Pill"
+            class="mr-1 text-green-500"
+            fill="currentColor"
+            focusable="false"
+            height="12"
+            role="img"
+            style="display: inline-block; user-select: none; vertical-align: text-bottom; overflow: visible;"
+            viewBox="0 0 16 16"
+            width="12"
+          >
+            <path
+              d="M8 0c4.42 0 8 3.58 8 8a8.013 8.013 0 0 1-5.45 7.59c-.4.08-.55-.17-.55-.38 0-.27.01-1.13.01-2.2 0-.75-.25-1.23-.54-1.48 1.78-.2 3.65-.88 3.65-3.95 0-.88-.31-1.59-.82-2.15.08-.2.36-1.02-.08-2.12 0 0-.67-.22-2.2.82-.64-.18-1.32-.27-2-.27-.68 0-1.36.09-2 .27-1.53-1.03-2.2-.82-2.2-.82-.44 1.1-.16 1.92-.08 2.12-.51.56-.82 1.28-.82 2.15 0 3.06 1.86 3.75 3.64 3.95-.23.2-.44.55-.51 1.07-.46.21-1.61.55-2.33-.66-.15-.24-.6-.83-1.23-.82-.67.01-.27.38.01.53.34.19.73.9.82 1.13.16.45.68 1.31 2.69.94 0 .67.01 1.3.01 1.49 0 .21-.15.45-.55.38A7.995 7.995 0 0 1 0 8c0-4.42 3.58-8 8-8Z"
+            />
+          </svg>
+          1
+        </button>
+      </span>
+    </div>
+  </body>,
+  "container": <div>
+    <span
+      title="Mock Pill"
+    >
+      <button
+        class="rounded-full text-xss px-1 m-0.5 bg-gray-100 hover:bg-gray-200 dark:bg-gray-800 dark:hover:bg-gray-700"
+        type="button"
+      >
+        <svg
+          aria-label="Mock Pill"
+          class="mr-1 text-green-500"
+          fill="currentColor"
+          focusable="false"
+          height="12"
+          role="img"
+          style="display: inline-block; user-select: none; vertical-align: text-bottom; overflow: visible;"
+          viewBox="0 0 16 16"
+          width="12"
+        >
+          <path
+            d="M8 0c4.42 0 8 3.58 8 8a8.013 8.013 0 0 1-5.45 7.59c-.4.08-.55-.17-.55-.38 0-.27.01-1.13.01-2.2 0-.75-.25-1.23-.54-1.48 1.78-.2 3.65-.88 3.65-3.95 0-.88-.31-1.59-.82-2.15.08-.2.36-1.02-.08-2.12 0 0-.67-.22-2.2.82-.64-.18-1.32-.27-2-.27-.68 0-1.36.09-2 .27-1.53-1.03-2.2-.82-2.2-.82-.44 1.1-.16 1.92-.08 2.12-.51.56-.82 1.28-.82 2.15 0 3.06 1.86 3.75 3.64 3.95-.23.2-.44.55-.51 1.07-.46.21-1.61.55-2.33-.66-.15-.24-.6-.83-1.23-.82-.67.01-.27.38.01.53.34.19.73.9.82 1.13.16.45.68 1.31 2.69.94 0 .67.01 1.3.01 1.49 0 .21-.15.45-.55.38A7.995 7.995 0 0 1 0 8c0-4.42 3.58-8 8-8Z"
+          />
+        </svg>
+        1
+      </button>
+    </span>
+  </div>,
+  "debug": [Function],
+  "findAllByAltText": [Function],
+  "findAllByDisplayValue": [Function],
+  "findAllByLabelText": [Function],
+  "findAllByPlaceholderText": [Function],
+  "findAllByRole": [Function],
+  "findAllByTestId": [Function],
+  "findAllByText": [Function],
+  "findAllByTitle": [Function],
+  "findByAltText": [Function],
+  "findByDisplayValue": [Function],
+  "findByLabelText": [Function],
+  "findByPlaceholderText": [Function],
+  "findByRole": [Function],
+  "findByTestId": [Function],
+  "findByText": [Function],
+  "findByTitle": [Function],
+  "getAllByAltText": [Function],
+  "getAllByDisplayValue": [Function],
+  "getAllByLabelText": [Function],
+  "getAllByPlaceholderText": [Function],
+  "getAllByRole": [Function],
+  "getAllByTestId": [Function],
+  "getAllByText": [Function],
+  "getAllByTitle": [Function],
+  "getByAltText": [Function],
+  "getByDisplayValue": [Function],
+  "getByLabelText": [Function],
+  "getByPlaceholderText": [Function],
+  "getByRole": [Function],
+  "getByTestId": [Function],
+  "getByText": [Function],
+  "getByTitle": [Function],
+  "queryAllByAltText": [Function],
+  "queryAllByDisplayValue": [Function],
+  "queryAllByLabelText": [Function],
+  "queryAllByPlaceholderText": [Function],
+  "queryAllByRole": [Function],
+  "queryAllByTestId": [Function],
+  "queryAllByText": [Function],
+  "queryAllByTitle": [Function],
+  "queryByAltText": [Function],
+  "queryByDisplayValue": [Function],
+  "queryByLabelText": [Function],
+  "queryByPlaceholderText": [Function],
+  "queryByRole": [Function],
+  "queryByTestId": [Function],
+  "queryByText": [Function],
+  "queryByTitle": [Function],
+  "rerender": [Function],
+  "unmount": [Function],
+}
+`;