Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
17 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion .storybook/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,10 @@ const config: StorybookConfig = {
"../public"
],

features: {
experimentalRSC: true,
},

docs: {
autodocs: true
},
Expand All @@ -30,4 +34,4 @@ const config: StorybookConfig = {
reactDocgen: "react-docgen-typescript"
}
};
export default config;
export default config;
13 changes: 13 additions & 0 deletions .storybook/preview-head.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
<link rel="preconnect" href="https://fonts.googleapis.com" />
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
<link
href="https://fonts.googleapis.com/css2?family=Inter:wght@100..900&display=swap"
rel="stylesheet"
/>
<style>
body {
font-family: "Inter", sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
</style>
36 changes: 35 additions & 1 deletion .storybook/preview.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,12 @@
import type { Preview } from "@storybook/react";
import "../src/app/globals.css";
import "@/app/[lang]/globals.css";
import {
getRouter,
usePathname,
} from "@storybook/nextjs/navigation.mock";
import mockRouter from "next-router-mock";
import { i18n } from "../src/i18n/i18n-config";

const preview: Preview = {
parameters: {
controls: {
Expand All @@ -8,6 +15,33 @@ const preview: Preview = {
date: /Date$/i,
},
},
nextjs: {
appDirectory: true,
},
},
beforeEach: () => {
getRouter().push.mockImplementation(
(...args: Parameters<typeof mockRouter.push>) => mockRouter.push(...args)
);
getRouter().replace.mockImplementation(
(...args: Parameters<typeof mockRouter.replace>) =>
mockRouter.replace(...args)
);

usePathname.mockImplementation(() => {
const pathname = mockRouter.pathname || `/${i18n.defaultLocale}`;
const segments = pathname.split("/");
const isLocaleMissing = i18n.locales.every(
(locale) =>
!pathname.startsWith(`/${locale}/`) && pathname !== `/${locale}`
);

if (isLocaleMissing) {
segments[1] = i18n.defaultLocale;
}

return segments.join("/");
});
},
};

Expand Down
39 changes: 39 additions & 0 deletions package-lock.json

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

6 changes: 5 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,14 @@
"test:e2e": "npx playwright test"
},
"dependencies": {
"@formatjs/intl-localematcher": "^0.6.1",
"@phosphor-icons/react": "^2.1.7",
"clsx": "^2.1.1",
"negotiator": "^1.0.0",
"next": "15.2.4",
"next-router-mock": "^0.9.13",
"react": "^19.0.0",
"react-dom": "^19.0.0",
"clsx": "^2.1.1",
"tailwind-merge": "^3.2.0"
},
"devDependencies": {
Expand All @@ -37,6 +40,7 @@
"@testing-library/dom": "^10.4.0",
"@testing-library/react": "^16.3.0",
"@testing-library/user-event": "^14.6.1",
"@types/negotiator": "^0.6.3",
"@types/node": "^20",
"@types/react": "^19",
"@types/react-dom": "^19",
Expand Down
File renamed without changes.
File renamed without changes.
31 changes: 31 additions & 0 deletions src/app/[lang]/layout.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import { i18n, type Locale } from "@/i18n/i18n-config";

import type { Metadata } from "next";
import { Inter } from "next/font/google";
import "./globals.css";

const inter = Inter({ subsets: ["latin"], variable: "--font-inter" });

export const metadata: Metadata = {
title: "E-Learning",
};

export async function generateStaticParams() {
return i18n.locales.map((locale) => ({ lang: locale }));
}

export default async function RootLayout({
children,
params,
}: Readonly<{
children: React.ReactNode;
params: Promise<{ lang: Locale }>;
}>) {
const { lang } = await params;

return (
<html lang={lang}>
<body className={`${inter.variable} antialiased`}>{children}</body>
</html>
);
}
25 changes: 25 additions & 0 deletions src/app/[lang]/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import { getDictionary } from "@/i18n/get-dictionary";
import { Locale } from "@/i18n/i18n-config";
import { Header } from "@/components/Header/Header";
import { Button } from "@/components/Button/Default/Button";

export default async function Home({ params }: { params: { lang: Locale } }) {
const { lang } = await params;

const dictionary = await getDictionary(lang);

return (
<div>
<Header lang={lang} dictionary={dictionary.header} />

<Button
text="Button"
variant="tinted"
color="danger"
disabled
iconPosition="start"
onlyIcon
/>
</div>
);
}
21 changes: 0 additions & 21 deletions src/app/layout.tsx

This file was deleted.

18 changes: 0 additions & 18 deletions src/app/page.tsx

This file was deleted.

26 changes: 26 additions & 0 deletions src/components/Header/Header.stories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import { Meta, StoryObj } from "@storybook/react";
import { Header } from "./Header";
import { getDictionary } from "@/i18n/get-dictionary";
import { i18n } from "@/i18n/i18n-config"; // Importa as configurações de idiomas

const meta: Meta<typeof Header> = {
title: "Components/Header",
component: Header,
argTypes: {
lang: {
control: "select",
options: i18n.locales,
},
},
};

export default meta;

type Story = StoryObj<typeof Header>;

export const Default: Story = {
args: {
lang: i18n.defaultLocale,
dictionary: (await getDictionary(i18n.defaultLocale)).header,
},
};
Empty file.
59 changes: 59 additions & 0 deletions src/components/Header/Header.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
"use client";

import { usePathname } from "next/navigation";
import Link from "next/link";
import { Typography } from "@/components/Typography/Typography";
import { cn } from "@/lib/utils";
import { type getDictionary } from "@/i18n/get-dictionary";
import LocaleSwitcher from "@/components/Header/LocaleSwitcher/LocaleSwitcher";

export const Header = ({
dictionary,
lang,
}: {
dictionary: Awaited<ReturnType<typeof getDictionary>>["header"];
lang: string;
}) => {
const pathname = usePathname();

const navItems = [
{ redirect: `/${lang}`, name: dictionary.nav.home },
{ redirect: `/${lang}/courses`, name: dictionary.nav.courses },
{ redirect: `/${lang}/about`, name: dictionary.nav.about },
{ redirect: `/${lang}/contact`, name: dictionary.nav.contact },
{ redirect: `/${lang}/instructor`, name: dictionary.nav.instructor },
];

const renderNavItems = () => {
return navItems.map((item) => {
const isActive = pathname.startsWith(item.redirect);

return (
<Typography
tag="li"
variant="text-body-md-500"
className={cn(
`cursor-pointer p-4 border-t-2 border-t-gray-900 text-gray-500 hover:text-white`,
isActive && "border-t-2 border-t-primary-500 text-white"
)}
key={item.redirect}
>
<Link href={item.redirect}>{item.name}</Link>
</Typography>
);
});
};

return (
<header className="flex items-center justify-between px-8 bg-gray-900">
<nav>
<ul className="flex gap-4">{renderNavItems()}</ul>
</nav>
<div className="flex gap-4 text-white">
{/* TODO implementar componente de select */}
<button>USD</button>
<LocaleSwitcher />
</div>
</header>
);
};
Loading