diff --git a/components/Common/AvatarGroup/__tests__/index.test.mjs b/components/Common/AvatarGroup/__tests__/index.test.mjs index 203f192dbcee2..7fded2f2265fd 100644 --- a/components/Common/AvatarGroup/__tests__/index.test.mjs +++ b/components/Common/AvatarGroup/__tests__/index.test.mjs @@ -26,7 +26,7 @@ const avatars = names.map(name => ({ alt: name, })); -describe('AvatarGroup component', () => { +describe('AvatarGroup', () => { it('renders the AvatarGroup component properly', () => { const { getByText } = render(); diff --git a/components/Common/LanguageDropDown/index.module.css b/components/Common/LanguageDropDown/index.module.css index c5dd3c9bd6e9e..cb76020aab1e5 100644 --- a/components/Common/LanguageDropDown/index.module.css +++ b/components/Common/LanguageDropDown/index.module.css @@ -1,12 +1,15 @@ -.iconWrapper { +.languageDropdown { @apply h-9 w-9 rounded-md - bg-neutral-100 p-2 text-neutral-700 - dark:bg-neutral-900 dark:text-neutral-300; + + &:hover { + @apply bg-neutral-100 + dark:bg-neutral-900; + } } .dropDownContent { diff --git a/components/Common/LanguageDropDown/index.tsx b/components/Common/LanguageDropDown/index.tsx index 5c2f81bf6d063..b41b3f1b7ef6d 100644 --- a/components/Common/LanguageDropDown/index.tsx +++ b/components/Common/LanguageDropDown/index.tsx @@ -30,7 +30,7 @@ const LanguageDropdown: FC = ({ return ( - diff --git a/components/Common/ThemeToggle/__tests__/index.test.mjs b/components/Common/ThemeToggle/__tests__/index.test.mjs new file mode 100644 index 0000000000000..d283a51f53f82 --- /dev/null +++ b/components/Common/ThemeToggle/__tests__/index.test.mjs @@ -0,0 +1,38 @@ +import { render, screen } from '@testing-library/react'; +import userEvent from '@testing-library/user-event'; + +import { LocaleProvider } from '@/providers/localeProvider'; + +import ThemeToggle from '../'; + +let mockCurrentTheme = 'light'; + +const toggleTheme = () => { + mockCurrentTheme = mockCurrentTheme === 'light' ? 'dark' : 'light'; +}; + +describe('ThemeToggle', () => { + let toggle; + + beforeEach(() => { + mockCurrentTheme = 'light'; + + render( + + + + ); + toggle = screen.getByRole('button'); + }); + + it('switches dark theme to light theme', async () => { + mockCurrentTheme = 'dark'; + await userEvent.click(toggle); + expect(mockCurrentTheme).toBe('light'); + }); + + it('switches light theme to dark theme', async () => { + await userEvent.click(toggle); + expect(mockCurrentTheme).toBe('dark'); + }); +}); diff --git a/components/Common/ThemeToggle/index.module.css b/components/Common/ThemeToggle/index.module.css new file mode 100644 index 0000000000000..8f95a0a9a4241 --- /dev/null +++ b/components/Common/ThemeToggle/index.module.css @@ -0,0 +1,13 @@ +.themeToggle { + @apply h-9 + w-9 + rounded-md + p-2 + text-neutral-700 + dark:text-neutral-300; + + &:hover { + @apply bg-neutral-100 + dark:bg-neutral-900; + } +} diff --git a/components/Common/ThemeToggle/index.stories.tsx b/components/Common/ThemeToggle/index.stories.tsx new file mode 100644 index 0000000000000..d249c438179ed --- /dev/null +++ b/components/Common/ThemeToggle/index.stories.tsx @@ -0,0 +1,10 @@ +import type { Meta as MetaObj, StoryObj } from '@storybook/react'; + +import ThemeToggle from '@/components/Common/ThemeToggle'; + +type Story = StoryObj; +type Meta = MetaObj; + +export const Default: Story = {}; + +export default { component: ThemeToggle } as Meta; diff --git a/components/Common/ThemeToggle/index.tsx b/components/Common/ThemeToggle/index.tsx new file mode 100644 index 0000000000000..bdba686edac88 --- /dev/null +++ b/components/Common/ThemeToggle/index.tsx @@ -0,0 +1,31 @@ +import { MoonIcon, SunIcon } from '@heroicons/react/24/outline'; +import type { FC, MouseEvent } from 'react'; +import { useIntl } from 'react-intl'; + +import styles from './index.module.css'; + +type ThemeToggleProps = { + onClick?: (event: MouseEvent) => void; +}; + +const ThemeToggle: FC = ({ onClick = () => {} }) => { + const { formatMessage } = useIntl(); + + const ariaLabel = formatMessage({ + id: 'components.header.buttons.toggleDarkMode', + }); + + return ( + + ); +}; + +export default ThemeToggle; diff --git a/package-lock.json b/package-lock.json index 5eabf2590993c..a175f85119b4d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -11,6 +11,7 @@ "@heroicons/react": "~2.0.18", "@mdx-js/react": "^2.3.0", "@nodevu/core": "~0.1.0", + "@radix-ui/react-accessible-icon": "^1.0.3", "@radix-ui/react-avatar": "^1.0.4", "@radix-ui/react-dropdown-menu": "^2.0.6", "@radix-ui/react-select": "^2.0.0", @@ -4438,6 +4439,29 @@ "@babel/runtime": "^7.13.10" } }, + "node_modules/@radix-ui/react-accessible-icon": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-accessible-icon/-/react-accessible-icon-1.0.3.tgz", + "integrity": "sha512-duVGKeWPSUILr/MdlPxV+GeULTc2rS1aihGdQ3N2qCUPMgxYLxvAsHJM3mCVLF8d5eK+ympmB22mb1F3a5biNw==", + "dependencies": { + "@babel/runtime": "^7.13.10", + "@radix-ui/react-visually-hidden": "1.0.3" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0", + "react-dom": "^16.8 || ^17.0 || ^18.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, "node_modules/@radix-ui/react-arrow": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/@radix-ui/react-arrow/-/react-arrow-1.0.3.tgz", diff --git a/package.json b/package.json index bf98b79b7c616..a7826c16c76ea 100644 --- a/package.json +++ b/package.json @@ -43,6 +43,7 @@ "@heroicons/react": "~2.0.18", "@mdx-js/react": "^2.3.0", "@nodevu/core": "~0.1.0", + "@radix-ui/react-accessible-icon": "^1.0.3", "@radix-ui/react-avatar": "^1.0.4", "@radix-ui/react-dropdown-menu": "^2.0.6", "@radix-ui/react-select": "^2.0.0",