From e2ac2ed50c3275492c26f0a58ac7464b918d7ec3 Mon Sep 17 00:00:00 2001 From: avivkeller Date: Tue, 8 Jul 2025 19:02:51 -0400 Subject: [PATCH] feat(ui): add NotificationProvider --- apps/site/components/Common/CodeBox.tsx | 3 +- .../__tests__/useNotification.test.jsx | 54 ------------------- apps/site/hooks/react-client/index.ts | 1 - .../hooks/react-client/useNotification.ts | 9 ---- apps/site/hooks/react-server/index.ts | 1 - .../hooks/react-server/useNotification.ts | 5 -- apps/site/layouts/Base.tsx | 2 +- apps/site/package.json | 1 - .../__tests__/notificationProvider.test.jsx | 43 --------------- .../src/Providers/NotificationProvider.tsx | 7 ++- .../__tests__/NotificationProvider.test.jsx | 53 ++++++++++++++++++ pnpm-lock.yaml | 3 -- 12 files changed, 61 insertions(+), 121 deletions(-) delete mode 100644 apps/site/hooks/react-client/__tests__/useNotification.test.jsx delete mode 100644 apps/site/hooks/react-client/useNotification.ts delete mode 100644 apps/site/hooks/react-server/useNotification.ts delete mode 100644 apps/site/providers/__tests__/notificationProvider.test.jsx rename apps/site/providers/notificationProvider.tsx => packages/ui-components/src/Providers/NotificationProvider.tsx (87%) create mode 100644 packages/ui-components/src/Providers/__tests__/NotificationProvider.test.jsx diff --git a/apps/site/components/Common/CodeBox.tsx b/apps/site/components/Common/CodeBox.tsx index 5d494fe825f3c..049e95196c4df 100644 --- a/apps/site/components/Common/CodeBox.tsx +++ b/apps/site/components/Common/CodeBox.tsx @@ -3,11 +3,12 @@ import { CodeBracketIcon } from '@heroicons/react/24/outline'; import BaseCodeBox from '@node-core/ui-components/Common/BaseCodeBox'; import styles from '@node-core/ui-components/Common/BaseCodeBox/index.module.css'; +import { useNotification } from '@node-core/ui-components/Providers/NotificationProvider'; import { useTranslations } from 'next-intl'; import type { FC, PropsWithChildren } from 'react'; import Link from '#site/components/Link'; -import { useCopyToClipboard, useNotification } from '#site/hooks'; +import { useCopyToClipboard } from '#site/hooks'; type CodeBoxProps = { language: string; diff --git a/apps/site/hooks/react-client/__tests__/useNotification.test.jsx b/apps/site/hooks/react-client/__tests__/useNotification.test.jsx deleted file mode 100644 index fcba40ea86091..0000000000000 --- a/apps/site/hooks/react-client/__tests__/useNotification.test.jsx +++ /dev/null @@ -1,54 +0,0 @@ -import { render } from '@testing-library/react'; - -import { describe, it } from 'node:test'; -import assert from 'node:assert/strict'; - -import useNotification from '#site/hooks/react-client/useNotification'; -import { NotificationProvider } from '#site/providers/notificationProvider'; - -describe('useNotification', () => { - it('should return the notification dispatch function', () => { - // Arrange - const TestComponent = () => { - const notificationDispatch = useNotification(); - return ( -
- {notificationDispatch ? 'Dispatch available' : 'Dispatch unavailable'} -
- ); - }; - - // Act - const { getByText } = render( - - - - ); - - // Assert - const result = getByText('Dispatch available'); - assert.ok(result.ownerDocument); - }); - - it('should return null outside NotificationProvider', () => { - // Arrange - const TestComponent = () => { - const notificationDispatch = useNotification(); - return ( -
- {notificationDispatch ? 'Dispatch available' : 'Dispatch unavailable'} -
- ); - }; - - // Act - const { queryByText } = render(); - - // Assert - const result = queryByText((content, element) => { - return element.textContent === 'Dispatch unavailable'; - }); - - assert.equal(result, null); - }); -}); diff --git a/apps/site/hooks/react-client/index.ts b/apps/site/hooks/react-client/index.ts index 33a9392db1a58..30d4e71478cea 100644 --- a/apps/site/hooks/react-client/index.ts +++ b/apps/site/hooks/react-client/index.ts @@ -1,6 +1,5 @@ export { default as useCopyToClipboard } from './useCopyToClipboard'; export { default as useDetectOS } from './useDetectOS'; export { default as useMediaQuery } from './useMediaQuery'; -export { default as useNotification } from './useNotification'; export { default as useClientContext } from './useClientContext'; export { default as useNavigationState } from './useNavigationState'; diff --git a/apps/site/hooks/react-client/useNotification.ts b/apps/site/hooks/react-client/useNotification.ts deleted file mode 100644 index acac47f606faf..0000000000000 --- a/apps/site/hooks/react-client/useNotification.ts +++ /dev/null @@ -1,9 +0,0 @@ -'use client'; - -import { useContext } from 'react'; - -import { NotificationDispatch } from '#site/providers/notificationProvider'; - -const useNotification = () => useContext(NotificationDispatch); - -export default useNotification; diff --git a/apps/site/hooks/react-server/index.ts b/apps/site/hooks/react-server/index.ts index a6cf9825ba2c2..e4c16cec3c466 100644 --- a/apps/site/hooks/react-server/index.ts +++ b/apps/site/hooks/react-server/index.ts @@ -1,5 +1,4 @@ export { default as useCopyToClipboard } from './useCopyToClipboard'; export { default as useDetectOS } from './useDetectOS'; export { default as useMediaQuery } from './useMediaQuery'; -export { default as useNotification } from './useNotification'; export { default as useClientContext } from './useClientContext'; diff --git a/apps/site/hooks/react-server/useNotification.ts b/apps/site/hooks/react-server/useNotification.ts deleted file mode 100644 index b85483fc24713..0000000000000 --- a/apps/site/hooks/react-server/useNotification.ts +++ /dev/null @@ -1,5 +0,0 @@ -const useNotification = () => { - throw new Error('Attempted to call useNotification from RSC'); -}; - -export default useNotification; diff --git a/apps/site/layouts/Base.tsx b/apps/site/layouts/Base.tsx index 83ff12826ed06..ab7770efb10ef 100644 --- a/apps/site/layouts/Base.tsx +++ b/apps/site/layouts/Base.tsx @@ -1,9 +1,9 @@ 'use client'; +import { NotificationProvider } from '@node-core/ui-components/Providers/NotificationProvider'; import type { FC, PropsWithChildren } from 'react'; import { NavigationStateProvider } from '#site/providers/navigationStateProvider'; -import { NotificationProvider } from '#site/providers/notificationProvider'; import styles from './layouts.module.css'; diff --git a/apps/site/package.json b/apps/site/package.json index bdd04d21b59d5..a0d9710f005a0 100644 --- a/apps/site/package.json +++ b/apps/site/package.json @@ -44,7 +44,6 @@ "@oramacloud/client": "^2.1.4", "@radix-ui/react-slot": "^1.2.3", "@radix-ui/react-tabs": "^1.1.12", - "@radix-ui/react-toast": "^1.2.14", "@radix-ui/react-tooltip": "^1.2.7", "@tailwindcss/postcss": "~4.1.11", "@types/node": "catalog:", diff --git a/apps/site/providers/__tests__/notificationProvider.test.jsx b/apps/site/providers/__tests__/notificationProvider.test.jsx deleted file mode 100644 index 9a95573ef7511..0000000000000 --- a/apps/site/providers/__tests__/notificationProvider.test.jsx +++ /dev/null @@ -1,43 +0,0 @@ -import { describe, it } from 'node:test'; -import assert from 'node:assert/strict'; - -import { render, act, waitFor } from '@testing-library/react'; -import userEvent from '@testing-library/user-event'; - -import { - NotificationProvider, - NotificationDispatch, -} from '#site/providers/notificationProvider'; - -describe('NotificationProvider', () => { - it('renders children and shows notification with the provided message', async t => { - t.mock.timers.enable(); - const testMessage = 'Test Notification'; - const testDuration = 3000; - - const { getByText } = render( - - - {dispatch => ( - - )} - - - ); - - act(() => { - userEvent.click(getByText('Show Notification')); - t.mock.timers.tick(3000); - }); - - t.mock.timers.reset(); - - await waitFor(() => assert.ok(getByText(testMessage).ownerDocument)); - }); -}); diff --git a/apps/site/providers/notificationProvider.tsx b/packages/ui-components/src/Providers/NotificationProvider.tsx similarity index 87% rename from apps/site/providers/notificationProvider.tsx rename to packages/ui-components/src/Providers/NotificationProvider.tsx index cd07fe6c1e029..743bc50c0f9a8 100644 --- a/apps/site/providers/notificationProvider.tsx +++ b/packages/ui-components/src/Providers/NotificationProvider.tsx @@ -1,5 +1,5 @@ -import Notification from '@node-core/ui-components/Common/Notification'; import * as Toast from '@radix-ui/react-toast'; +import { createContext, useContext, useEffect, useState } from 'react'; import type { Dispatch, FC, @@ -7,7 +7,8 @@ import type { ReactNode, SetStateAction, } from 'react'; -import { createContext, useEffect, useState } from 'react'; + +import Notification from '#ui/Common/Notification'; type NotificationContextType = { message: string | ReactNode; @@ -22,6 +23,8 @@ export const NotificationDispatch = createContext< Dispatch> >(() => {}); +export const useNotification = () => useContext(NotificationDispatch); + export const NotificationProvider: FC> = ({ viewportClassName, children, diff --git a/packages/ui-components/src/Providers/__tests__/NotificationProvider.test.jsx b/packages/ui-components/src/Providers/__tests__/NotificationProvider.test.jsx new file mode 100644 index 0000000000000..33525f1f42509 --- /dev/null +++ b/packages/ui-components/src/Providers/__tests__/NotificationProvider.test.jsx @@ -0,0 +1,53 @@ +import { render } from '@testing-library/react'; + +import { describe, it } from 'node:test'; +import assert from 'node:assert/strict'; + +import { NotificationProvider, useNotification } from '../NotificationProvider'; + +describe('useNotification', () => { + it('should return the notification dispatch function', () => { + // Arrange + const TestComponent = () => { + const notificationDispatch = useNotification(); + return ( +
+ {notificationDispatch ? 'Dispatch available' : 'Dispatch unavailable'} +
+ ); + }; + + // Act + const { getByText } = render( + + + + ); + + // Assert + const result = getByText('Dispatch available'); + assert.ok(result.ownerDocument); + }); + + it('should return null outside NotificationProvider', () => { + // Arrange + const TestComponent = () => { + const notificationDispatch = useNotification(); + return ( +
+ {notificationDispatch ? 'Dispatch available' : 'Dispatch unavailable'} +
+ ); + }; + + // Act + const { queryByText } = render(); + + // Assert + const result = queryByText((content, element) => { + return element.textContent === 'Dispatch unavailable'; + }); + + assert.equal(result, null); + }); +}); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 9b3dfd68b48b0..6df9fdce2ce2c 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -111,9 +111,6 @@ importers: '@radix-ui/react-tabs': specifier: ^1.1.12 version: 1.1.12(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) - '@radix-ui/react-toast': - specifier: ^1.2.14 - version: 1.2.14(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) '@radix-ui/react-tooltip': specifier: ^1.2.7 version: 1.2.7(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)