Skip to content

Commit

Permalink
feat: make user dashboard responsive
Browse files Browse the repository at this point in the history
  • Loading branch information
ixartz committed May 15, 2024
1 parent f3dc1da commit f88c9dd
Show file tree
Hide file tree
Showing 9 changed files with 151 additions and 34 deletions.
20 changes: 17 additions & 3 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,7 @@
"@storybook/test-runner": "^0.17.0",
"@testing-library/jest-dom": "^6.4.5",
"@testing-library/react": "^15.0.7",
"@testing-library/user-event": "^14.5.2",
"@types/jest": "^29.5.12",
"@types/node": "^20.12.11",
"@types/react": "^18.3.1",
Expand Down
20 changes: 19 additions & 1 deletion src/app/[locale]/(auth)/dashboard/layout.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { useTranslations } from 'next-intl';
import { getTranslations } from 'next-intl/server';

import { DashboardHeader } from '@/features/dashboard/DashboardHeader';
Expand All @@ -15,11 +16,28 @@ export async function generateMetadata(props: { params: { locale: string } }) {
}

export default function DashboardLayout(props: { children: React.ReactNode }) {
const t = useTranslations('DashboardLayout');

return (
<>
<div className="shadow-md">
<div className="mx-auto flex max-w-screen-xl items-center justify-between px-3 py-4">
<DashboardHeader />
<DashboardHeader
menu={[
{
href: '/dashboard',
label: t('home'),
},
{
href: '/dashboard/organization-profile/organization-members',
label: t('members'),
},
{
href: '/dashboard/organization-profile',
label: t('settings'),
},
]}
/>
</div>
</div>

Expand Down
5 changes: 3 additions & 2 deletions src/components/LocaleSwitcher.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import { useLocale } from 'next-intl';

import { Button } from '@/components/ui/button';
import {
DropdownMenu,
DropdownMenuContent,
Expand All @@ -25,7 +26,7 @@ export default function LocaleSwitcher() {
return (
<DropdownMenu>
<DropdownMenuTrigger asChild>
<div className="cursor-pointer px-1 py-2">
<Button className="p-2 focus-visible:ring-offset-0" variant="ghost">
<svg
xmlns="http://www.w3.org/2000/svg"
className="size-6 stroke-current stroke-2"
Expand All @@ -38,7 +39,7 @@ export default function LocaleSwitcher() {
<path d="M3 12a9 9 0 1 0 18 0 9 9 0 0 0-18 0M3.6 9h16.8M3.6 15h16.8" />
<path d="M11.5 3a17 17 0 0 0 0 18M12.5 3a17 17 0 0 1 0 18" />
</svg>
</div>
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent>
<DropdownMenuRadioGroup value={locale} onValueChange={handleChange}>
Expand Down
18 changes: 18 additions & 0 deletions src/components/ToggleMenuButton.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { render, screen } from '@testing-library/react';
import userEvent from '@testing-library/user-event';

import { ToggleMenuButton } from './ToggleMenuButton';

describe('ToggleMenuButton', () => {
describe('onClick props', () => {
it('should call the callback when the user click on the button', async () => {
const handler = jest.fn();

render(<ToggleMenuButton onClick={handler} />);
const button = screen.getByRole('button');
await userEvent.click(button);

expect(handler).toHaveBeenCalled();
});
});
});
24 changes: 17 additions & 7 deletions src/components/ToggleMenuButton.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
import { type ForwardedRef, forwardRef } from 'react';

import { Button } from '@/components/ui/button';

/* eslint-disable jsx-a11y/control-has-associated-label */
type IToggleMenuButtonProps = {
onClick: () => void;
onClick?: () => void;
};

/**
Expand All @@ -9,11 +13,15 @@ type IToggleMenuButtonProps = {
* @params props - Component props.
* @param props.onClick - Function to run when the button is clicked.
*/
const ToggleMenuButton = (props: IToggleMenuButtonProps) => (
<button
className="rounded-md px-1 py-2 hover:bg-white"
onClick={props.onClick}
type="button"
const ToggleMenuButtonInternal = (
props: IToggleMenuButtonProps,
ref?: ForwardedRef<HTMLButtonElement>,
) => (
<Button
className="p-2 focus-visible:ring-offset-0"
variant="ghost"
ref={ref}
{...props}
>
<svg
className="size-6 stroke-current"
Expand All @@ -27,7 +35,9 @@ const ToggleMenuButton = (props: IToggleMenuButtonProps) => (
<path d="M0 0h24v24H0z" stroke="none" />
<path d="M4 6h16M4 12h16M4 18h16" />
</svg>
</button>
</Button>
);

const ToggleMenuButton = forwardRef(ToggleMenuButtonInternal);

export { ToggleMenuButton };
51 changes: 31 additions & 20 deletions src/features/dashboard/DashboardHeader.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,26 @@

import { OrganizationSwitcher, UserButton } from '@clerk/nextjs';
import Link from 'next/link';
import { useLocale, useTranslations } from 'next-intl';
import { useLocale } from 'next-intl';

import { ActiveLink } from '@/components/ActiveLink';
import LocaleSwitcher from '@/components/LocaleSwitcher';
import { ToggleMenuButton } from '@/components/ToggleMenuButton';
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuTrigger,
} from '@/components/ui/dropdown-menu';
import { Logo } from '@/templates/Logo';
import { getI18nPath } from '@/utils/Helpers';

const DashboardHeader = () => {
const t = useTranslations('DashboardLayout');
const DashboardHeader = (props: {
menu: {
href: string;
label: string;
}[];
}) => {
const locale = useLocale();

return (
Expand Down Expand Up @@ -51,30 +61,31 @@ const DashboardHeader = () => {

<nav className="ml-3 max-lg:hidden">
<ul className="flex flex-row items-center gap-x-3 text-lg font-medium [&_a:hover]:opacity-100 [&_a]:opacity-75">
<li>
<ActiveLink href="/dashboard">{t('home')}</ActiveLink>
</li>

<li>
<ActiveLink href="/dashboard/organization-profile/organization-members">
{t('members')}
</ActiveLink>
</li>

<li>
<ActiveLink href="/dashboard/organization-profile">
{t('settings')}
</ActiveLink>
</li>
{props.menu.map((item) => (
<li key={item.href}>
<ActiveLink href={item.href}>{item.label}</ActiveLink>
</li>
))}
</ul>
</nav>
</div>

<div className="">
<div>
<ul className="flex items-center gap-x-1 [&_li:not(:last-child):hover]:opacity-100 [&_li:not(:last-child)]:opacity-60">
<li>
<div className="lg:hidden">
<ToggleMenuButton onClick={() => {}} />
<DropdownMenu>
<DropdownMenuTrigger asChild>
<ToggleMenuButton />
</DropdownMenuTrigger>
<DropdownMenuContent>
{props.menu.map((item) => (
<DropdownMenuItem key={item.href} asChild>
<Link href={item.href}>{item.label}</Link>
</DropdownMenuItem>
))}
</DropdownMenuContent>
</DropdownMenu>
</div>
</li>

Expand Down
2 changes: 1 addition & 1 deletion src/features/landing/CenteredMenu.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ const CenteredMenu = (props: {
navClass,
)}
>
<ul className="flex flex-row items-center gap-x-3 text-lg font-medium [&_li:not(:last-child):hover]:opacity-100 [&_li:not(:last-child)]:opacity-60">
<ul className="flex flex-row items-center gap-x-4 text-lg font-medium [&_li:not(:last-child):hover]:opacity-100 [&_li:not(:last-child)]:opacity-60">
{props.rightMenu}
</ul>
</div>
Expand Down
44 changes: 44 additions & 0 deletions src/hooks/UseMenu.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import { act, renderHook } from '@testing-library/react';

import { useMenu } from './UseMenu';

describe('UseMenu', () => {
describe('Render hook', () => {
it("shouldn't show the menu by default", async () => {
const { result } = renderHook(() => useMenu());

expect(result.current.showMenu).toBeFalsy();
});

it('should make the menu visible by toggling the menu', () => {
const { result } = renderHook(() => useMenu());

act(() => {
result.current.handleToggleMenu();
});

expect(result.current.showMenu).toBeTruthy();
});

it("shouldn't make the menu visible after toggling and closing the menu", () => {
const { result } = renderHook(() => useMenu());

act(() => {
result.current.handleClose();
});

expect(result.current.showMenu).toBeFalsy();
});

it("shouldn't make the menu visible after toggling the menu twice", () => {
const { result } = renderHook(() => useMenu());

act(() => {
result.current.handleToggleMenu();
result.current.handleToggleMenu();
});

expect(result.current.showMenu).toBeFalsy();
});
});
});

0 comments on commit f88c9dd

Please sign in to comment.