refactor: components struture
This commit is contained in:
parent
90a8a50ad0
commit
33b57bf173
2
.vscode/react.code-snippets
vendored
2
.vscode/react.code-snippets
vendored
@ -3,7 +3,7 @@
|
||||
"scope": "typescriptreact",
|
||||
"prefix": "rfc",
|
||||
"body": [
|
||||
"interface ${1:ComponentName}Props {}",
|
||||
"export interface ${1:ComponentName}Props {}",
|
||||
"",
|
||||
"export const ${1:ComponentName}: React.FC<${1:ComponentName}Props> = () => {",
|
||||
" return (",
|
||||
|
@ -1,9 +1,9 @@
|
||||
import "@repo/config-tailwind/styles.css"
|
||||
import { defaultTranslationValues, Locale } from "@repo/i18n/config"
|
||||
import i18nMessagesEnglish from "@repo/i18n/translations/en-US.json"
|
||||
import { ThemeProvider } from "@repo/ui/Header/SwitchTheme"
|
||||
import type { Preview } from "@storybook/react"
|
||||
import { NextIntlClientProvider } from "next-intl"
|
||||
import { ThemeProvider as NextThemeProvider } from "next-themes"
|
||||
import React from "react"
|
||||
|
||||
const preview: Preview = {
|
||||
@ -13,7 +13,7 @@ const preview: Preview = {
|
||||
},
|
||||
options: {
|
||||
storySort: {
|
||||
order: ["Design System", "User Interface", "Feature"],
|
||||
order: ["Design System", "Layout", "Errors"],
|
||||
},
|
||||
},
|
||||
backgrounds: { disable: true },
|
||||
@ -35,7 +35,7 @@ const preview: Preview = {
|
||||
const locale = "en-US" satisfies Locale
|
||||
|
||||
return (
|
||||
<ThemeProvider>
|
||||
<NextThemeProvider enableColorScheme={false}>
|
||||
<NextIntlClientProvider
|
||||
messages={i18nMessagesEnglish}
|
||||
locale={locale}
|
||||
@ -43,7 +43,7 @@ const preview: Preview = {
|
||||
>
|
||||
<Story />
|
||||
</NextIntlClientProvider>
|
||||
</ThemeProvider>
|
||||
</NextThemeProvider>
|
||||
)
|
||||
},
|
||||
],
|
||||
|
@ -19,6 +19,7 @@
|
||||
"@repo/wikipedia-game-solver": "workspace:*",
|
||||
"next": "catalog:",
|
||||
"next-intl": "catalog:",
|
||||
"next-themes": "catalog:",
|
||||
"react": "catalog:",
|
||||
"react-dom": "catalog:"
|
||||
},
|
||||
|
@ -5,7 +5,7 @@ RUN corepack enable
|
||||
WORKDIR /usr/src/app
|
||||
|
||||
FROM node-pnpm AS builder
|
||||
RUN pnpm install --global turbo@2.0.9
|
||||
RUN pnpm install --global turbo@2.0.10
|
||||
COPY ./ ./
|
||||
RUN turbo prune @repo/website --docker
|
||||
|
||||
|
@ -1,34 +1,10 @@
|
||||
"use client"
|
||||
|
||||
import { Button } from "@repo/ui/design/Button"
|
||||
import { MainLayout } from "@repo/ui/MainLayout"
|
||||
import { useTranslations } from "next-intl"
|
||||
import { useEffect } from "react"
|
||||
import type { ErrorServerProps } from "@repo/ui/Errors/ErrorServer"
|
||||
import { ErrorServer } from "@repo/ui/Errors/ErrorServer"
|
||||
|
||||
interface ErrorBoundaryPageProps {
|
||||
error: Error & { digest?: string }
|
||||
reset: () => void
|
||||
}
|
||||
|
||||
const ErrorBoundaryPage: React.FC<ErrorBoundaryPageProps> = (props) => {
|
||||
const { error, reset } = props
|
||||
|
||||
const t = useTranslations()
|
||||
|
||||
useEffect(() => {
|
||||
console.error(error)
|
||||
}, [error])
|
||||
|
||||
return (
|
||||
<MainLayout className="items-center justify-center text-center">
|
||||
<h1 className="text-3xl font-semibold">
|
||||
{t("errors.error")} 500 - {t("errors.server-error")}
|
||||
</h1>
|
||||
<p className="mb-4 mt-2">
|
||||
<Button onClick={reset}>{t("errors.try-again")}</Button>
|
||||
</p>
|
||||
</MainLayout>
|
||||
)
|
||||
const ErrorBoundaryPage: React.FC<ErrorServerProps> = (props) => {
|
||||
return <ErrorServer {...props} />
|
||||
}
|
||||
|
||||
export default ErrorBoundaryPage
|
||||
|
@ -1,10 +1,10 @@
|
||||
import "@repo/config-tailwind/styles.css"
|
||||
import { VERSION } from "@repo/utils/constants"
|
||||
import type { Locale, LocaleProps } from "@repo/i18n/config"
|
||||
import { LOCALES } from "@repo/i18n/config"
|
||||
import { Footer } from "@repo/ui/Footer"
|
||||
import { Header } from "@repo/ui/Header"
|
||||
import { ThemeProvider } from "@repo/ui/Header/SwitchTheme"
|
||||
import { Footer } from "@repo/ui/Layout/Footer"
|
||||
import { Header } from "@repo/ui/Layout/Header"
|
||||
import { ThemeProvider } from "@repo/ui/Layout/Header/SwitchTheme"
|
||||
import { VERSION } from "@repo/utils/constants"
|
||||
import type { Metadata } from "next"
|
||||
import { NextIntlClientProvider } from "next-intl"
|
||||
import {
|
||||
@ -17,9 +17,37 @@ export const generateMetadata = async ({
|
||||
params,
|
||||
}: LocaleProps): Promise<Metadata> => {
|
||||
const t = await getTranslations({ locale: params.locale })
|
||||
const title = t("meta.title")
|
||||
const description = t("meta.description")
|
||||
const image = "/images/Wikipedia-Logo.webp"
|
||||
const url = new URL("https://wikipedia-game-solver.theoludwig.fr")
|
||||
const locale = LOCALES.join(", ")
|
||||
|
||||
return {
|
||||
title: t("meta.title"),
|
||||
description: t("meta.description"),
|
||||
title,
|
||||
description,
|
||||
metadataBase: url,
|
||||
openGraph: {
|
||||
title,
|
||||
description,
|
||||
url,
|
||||
siteName: title,
|
||||
images: [
|
||||
{
|
||||
url: image,
|
||||
width: 96,
|
||||
height: 96,
|
||||
},
|
||||
],
|
||||
locale,
|
||||
type: "website",
|
||||
},
|
||||
twitter: {
|
||||
card: "summary",
|
||||
title,
|
||||
description,
|
||||
images: [image],
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,9 +1,9 @@
|
||||
import { MainLayout } from "@repo/ui/MainLayout"
|
||||
import { Spinner } from "@repo/ui/design/Spinner"
|
||||
import { Spinner } from "@repo/ui/Design/Spinner"
|
||||
import { MainLayout } from "@repo/ui/Layout/MainLayout"
|
||||
|
||||
const Loading: React.FC = () => {
|
||||
return (
|
||||
<MainLayout className="items-center justify-center">
|
||||
<MainLayout center>
|
||||
<Spinner size={50} />
|
||||
</MainLayout>
|
||||
)
|
||||
|
@ -1,24 +1,10 @@
|
||||
import { Link } from "@repo/ui/design/Link"
|
||||
import { MainLayout } from "@repo/ui/MainLayout"
|
||||
import { useTranslations } from "next-intl"
|
||||
import { ErrorNotFound } from "@repo/ui/Errors/ErrorNotFound"
|
||||
|
||||
/**
|
||||
* Note that `app/[locale]/[...rest]/page.tsx` is necessary for this page to render.
|
||||
*/
|
||||
const NotFound: React.FC = () => {
|
||||
const t = useTranslations()
|
||||
|
||||
return (
|
||||
<MainLayout className="items-center justify-center text-center">
|
||||
<h1 className="text-3xl font-semibold">
|
||||
{t("errors.error")} 404 - {t("errors.not-found")}
|
||||
</h1>
|
||||
<p className="mb-4 mt-2">
|
||||
{t("errors.page-doesnt-exist")}{" "}
|
||||
<Link href="/">{t("errors.return-to-home-page")}</Link>
|
||||
</p>
|
||||
</MainLayout>
|
||||
)
|
||||
return <ErrorNotFound />
|
||||
}
|
||||
|
||||
export default NotFound
|
||||
|
@ -1,13 +1,17 @@
|
||||
import type { LocaleProps } from "@repo/i18n/config"
|
||||
import { Link } from "@repo/ui/design/Link"
|
||||
import { Typography } from "@repo/ui/design/Typography"
|
||||
import { MainLayout } from "@repo/ui/MainLayout"
|
||||
import { Link } from "@repo/ui/Design/Link"
|
||||
import { Typography } from "@repo/ui/Design/Typography"
|
||||
import { MainLayout } from "@repo/ui/Layout/MainLayout"
|
||||
import {
|
||||
fromLocaleToWikipediaLocale,
|
||||
getWikipediaLink,
|
||||
} from "@repo/wikipedia-game-solver/wikipedia-api"
|
||||
import { WikipediaClient } from "@repo/wikipedia-game-solver/WikipediaClient"
|
||||
import { useTranslations } from "next-intl"
|
||||
import { unstable_setRequestLocale } from "next-intl/server"
|
||||
import Image from "next/image"
|
||||
|
||||
import WikipediaLogo from "#public/images/Wikipedia-Logo.png"
|
||||
import WikipediaLogo from "#public/images/Wikipedia-Logo.webp"
|
||||
|
||||
interface HomePageProps extends LocaleProps {}
|
||||
|
||||
@ -19,6 +23,8 @@ const HomePage: React.FC<HomePageProps> = (props) => {
|
||||
|
||||
const t = useTranslations()
|
||||
|
||||
const localeWikipedia = fromLocaleToWikipediaLocale(params.locale)
|
||||
|
||||
return (
|
||||
<MainLayout>
|
||||
<section className="text-center">
|
||||
@ -28,9 +34,9 @@ const HomePage: React.FC<HomePageProps> = (props) => {
|
||||
|
||||
<Typography as="p" variant="text1" className="mt-3">
|
||||
{t.rich("home.description", {
|
||||
wikipedia: (children) => {
|
||||
"wikipedia-link": (children) => {
|
||||
return (
|
||||
<Link href="https://en.wikipedia.org/" target="_blank">
|
||||
<Link href={getWikipediaLink(localeWikipedia)} target="_blank">
|
||||
{children}
|
||||
</Link>
|
||||
)
|
||||
|
@ -14,6 +14,8 @@ export const config = {
|
||||
|
||||
// Set a cookie to remember the previous locale for
|
||||
// all requests that have a locale prefix
|
||||
// Next.js issue, middleware matcher should support template literals:
|
||||
// https://github.com/vercel/next.js/issues/56398
|
||||
"/(en-US|fr-FR)/:path*",
|
||||
|
||||
// Enable redirects that add missing locales
|
||||
|
@ -23,7 +23,7 @@
|
||||
"next-intl": "catalog:",
|
||||
"react": "catalog:",
|
||||
"react-dom": "catalog:",
|
||||
"react-icons": "catalog:"
|
||||
"sharp": "catalog:"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@repo/eslint-config": "workspace:*",
|
||||
|
Binary file not shown.
Before Width: | Height: | Size: 272 KiB |
BIN
apps/website/public/images/Wikipedia-Logo.webp
Normal file
BIN
apps/website/public/images/Wikipedia-Logo.webp
Normal file
Binary file not shown.
After Width: | Height: | Size: 91 KiB |
@ -24,11 +24,12 @@
|
||||
"@semantic-release/exec": "6.0.3",
|
||||
"@semantic-release/git": "10.0.1",
|
||||
"editorconfig-checker": "5.1.8",
|
||||
"playwright": "catalog:",
|
||||
"prettier": "3.3.3",
|
||||
"prettier-plugin-tailwindcss": "0.6.5",
|
||||
"replace-in-files-cli": "3.0.0",
|
||||
"semantic-release": "23.1.1",
|
||||
"turbo": "2.0.9",
|
||||
"turbo": "2.0.10",
|
||||
"typescript": "catalog:"
|
||||
}
|
||||
}
|
||||
|
@ -1,13 +1,15 @@
|
||||
{
|
||||
"meta": {
|
||||
"title": "Wikipedia Game Solver",
|
||||
"description": "The Wikipedia Game involves players competing to navigate from one Wikipedia page to another using only internal links.",
|
||||
"all-rights-reserved": "All rights reserved"
|
||||
"description": "The Wikipedia Game involves players competing to navigate from one Wikipedia page to another using only internal links."
|
||||
},
|
||||
"locales": {
|
||||
"en-US": "🇺🇸 English",
|
||||
"fr-FR": "🇫🇷 French"
|
||||
},
|
||||
"footer": {
|
||||
"all-rights-reserved": "All rights reserved"
|
||||
},
|
||||
"errors": {
|
||||
"error": "Error",
|
||||
"page-doesnt-exist": "This page doesn't exist!",
|
||||
@ -18,6 +20,6 @@
|
||||
},
|
||||
"home": {
|
||||
"title": "Wikipedia Game Solver",
|
||||
"description": "The Wikipedia Game involves players competing to navigate from one <wikipedia>Wikipedia</wikipedia> page to another using only internal links."
|
||||
"description": "The Wikipedia Game involves players competing to navigate from one <wikipedia-link>Wikipedia</wikipedia-link> page to another using only internal links."
|
||||
}
|
||||
}
|
||||
|
@ -1,13 +1,15 @@
|
||||
{
|
||||
"meta": {
|
||||
"title": "Wikipedia Game Solver",
|
||||
"description": "Le jeu Wikipédia implique des joueurs en compétition pour naviguer d'une page Wikipédia à une autre en utilisant uniquement des liens internes.",
|
||||
"all-rights-reserved": "Tous droits réservés"
|
||||
"description": "Le jeu Wikipédia implique des joueurs en compétition pour naviguer d'une page Wikipédia à une autre en utilisant uniquement des liens internes."
|
||||
},
|
||||
"locales": {
|
||||
"en-US": "🇺🇸 Anglais",
|
||||
"fr-FR": "🇫🇷 Français"
|
||||
},
|
||||
"footer": {
|
||||
"all-rights-reserved": "Tous droits réservés"
|
||||
},
|
||||
"errors": {
|
||||
"error": "Erreur",
|
||||
"page-doesnt-exist": "Cette page n'existe pas !",
|
||||
@ -18,6 +20,6 @@
|
||||
},
|
||||
"home": {
|
||||
"title": "Wikipedia Game Solver",
|
||||
"description": "Le jeu Wikipédia implique des joueurs en compétition pour naviguer d'une page <wikipedia>Wikipedia</wikipedia> à une autre en utilisant uniquement des liens internes."
|
||||
"description": "Le jeu Wikipédia implique des joueurs en compétition pour naviguer d'une page <wikipedia-link>Wikipedia</wikipedia-link> à une autre en utilisant uniquement des liens internes."
|
||||
}
|
||||
}
|
||||
|
@ -4,14 +4,17 @@
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"exports": {
|
||||
"./design/Button": "./src/design/Button/Button.tsx",
|
||||
"./design/Link": "./src/design/Link/Link.tsx",
|
||||
"./design/Spinner": "./src/design/Spinner/Spinner.tsx",
|
||||
"./design/Typography": "./src/design/Typography/Typography.tsx",
|
||||
"./Footer": "./src/Footer/Footer.tsx",
|
||||
"./Header": "./src/Header/Header.tsx",
|
||||
"./Header/SwitchTheme": "./src/Header/SwitchTheme.tsx",
|
||||
"./MainLayout": "./src/MainLayout/MainLayout.tsx"
|
||||
"./Design/Button": "./src/Design/Button/Button.tsx",
|
||||
"./Design/Link": "./src/Design/Link/Link.tsx",
|
||||
"./Design/Spinner": "./src/Design/Spinner/Spinner.tsx",
|
||||
"./Design/Typography": "./src/Design/Typography/Typography.tsx",
|
||||
"./Errors/ErrorNotFound": "./src/Errors/ErrorNotFound/ErrorNotFound.tsx",
|
||||
"./Errors/ErrorServer": "./src/Errors/ErrorServer/ErrorServer.tsx",
|
||||
"./Layout/Footer": "./src/Layout/Footer/Footer.tsx",
|
||||
"./Layout/Header": "./src/Layout/Header/Header.tsx",
|
||||
"./Layout/Header/SwitchTheme": "./src/Layout/Header/SwitchTheme.tsx",
|
||||
"./Layout/MainLayout": "./src/Layout/MainLayout/MainLayout.tsx",
|
||||
"./Layout/Section": "./src/Layout/Section/Section.tsx"
|
||||
},
|
||||
"scripts": {
|
||||
"lint:eslint": "eslint src --max-warnings 0 --report-unused-disable-directives",
|
||||
|
@ -11,8 +11,8 @@ const typographyVariants = cva({
|
||||
h4: "text-xl font-semibold",
|
||||
h5: "text-xl font-medium",
|
||||
h6: "text-lg font-medium",
|
||||
text1: "text-base",
|
||||
text2: "text-sm",
|
||||
text1: "break-words text-base",
|
||||
text2: "break-words text-sm",
|
||||
},
|
||||
},
|
||||
})
|
@ -0,0 +1,16 @@
|
||||
import type { Meta, StoryObj } from "@storybook/react"
|
||||
|
||||
import { ErrorNotFound as ErrorNotFoundComponent } from "./ErrorNotFound"
|
||||
|
||||
const meta = {
|
||||
title: "Errors/ErrorNotFound",
|
||||
component: ErrorNotFoundComponent,
|
||||
} satisfies Meta<typeof ErrorNotFoundComponent>
|
||||
|
||||
export default meta
|
||||
|
||||
type Story = StoryObj<typeof meta>
|
||||
|
||||
export const ErrorNotFound: Story = {
|
||||
args: {},
|
||||
}
|
26
packages/ui/src/Errors/ErrorNotFound/ErrorNotFound.tsx
Normal file
26
packages/ui/src/Errors/ErrorNotFound/ErrorNotFound.tsx
Normal file
@ -0,0 +1,26 @@
|
||||
import { useTranslations } from "next-intl"
|
||||
import { Link } from "../../Design/Link/Link"
|
||||
import { Typography } from "../../Design/Typography/Typography"
|
||||
import { MainLayout } from "../../Layout/MainLayout/MainLayout"
|
||||
import { Section } from "../../Layout/Section/Section"
|
||||
|
||||
export interface ErrorNotFoundProps {}
|
||||
|
||||
export const ErrorNotFound: React.FC<ErrorNotFoundProps> = () => {
|
||||
const t = useTranslations()
|
||||
|
||||
return (
|
||||
<MainLayout center>
|
||||
<Section horizontalSpacing>
|
||||
<Typography variant="h1" as="h1">
|
||||
{t("errors.error")} 404 - {t("errors.not-found")}
|
||||
</Typography>
|
||||
|
||||
<Typography variant="text1" as="p" className="mt-4">
|
||||
{t("errors.page-doesnt-exist")}{" "}
|
||||
<Link href="/">{t("errors.return-to-home-page")}</Link>
|
||||
</Typography>
|
||||
</Section>
|
||||
</MainLayout>
|
||||
)
|
||||
}
|
22
packages/ui/src/Errors/ErrorServer/ErrorServer.stories.tsx
Normal file
22
packages/ui/src/Errors/ErrorServer/ErrorServer.stories.tsx
Normal file
@ -0,0 +1,22 @@
|
||||
import type { Meta, StoryObj } from "@storybook/react"
|
||||
import { expect, fn, userEvent, within } from "@storybook/test"
|
||||
|
||||
import { ErrorServer as ErrorServerComponent } from "./ErrorServer"
|
||||
|
||||
const meta = {
|
||||
title: "Errors/ErrorServer",
|
||||
component: ErrorServerComponent,
|
||||
} satisfies Meta<typeof ErrorServerComponent>
|
||||
|
||||
export default meta
|
||||
|
||||
type Story = StoryObj<typeof meta>
|
||||
|
||||
export const ErrorServer: Story = {
|
||||
args: { reset: fn(), error: new Error("Server error") },
|
||||
play: async ({ canvasElement, args }) => {
|
||||
const canvas = within(canvasElement)
|
||||
await userEvent.click(canvas.getByText("Try again?"))
|
||||
await expect(args.reset).toHaveBeenCalled()
|
||||
},
|
||||
}
|
37
packages/ui/src/Errors/ErrorServer/ErrorServer.tsx
Normal file
37
packages/ui/src/Errors/ErrorServer/ErrorServer.tsx
Normal file
@ -0,0 +1,37 @@
|
||||
"use client"
|
||||
|
||||
import { useTranslations } from "next-intl"
|
||||
import { useEffect } from "react"
|
||||
import { Button } from "../../Design/Button/Button"
|
||||
import { Typography } from "../../Design/Typography/Typography"
|
||||
import { MainLayout } from "../../Layout/MainLayout/MainLayout"
|
||||
import { Section } from "../../Layout/Section/Section"
|
||||
|
||||
export interface ErrorServerProps {
|
||||
error: Error & { digest?: string }
|
||||
reset: () => void
|
||||
}
|
||||
|
||||
export const ErrorServer: React.FC<ErrorServerProps> = (props) => {
|
||||
const { error, reset } = props
|
||||
|
||||
const t = useTranslations()
|
||||
|
||||
useEffect(() => {
|
||||
console.error(error)
|
||||
}, [error])
|
||||
|
||||
return (
|
||||
<MainLayout center>
|
||||
<Section horizontalSpacing>
|
||||
<Typography variant="h1" as="h1">
|
||||
{t("errors.error")} 500 - {t("errors.server-error")}
|
||||
</Typography>
|
||||
|
||||
<Typography variant="text1" as="p" className="mt-4">
|
||||
<Button onClick={reset}>{t("errors.try-again")}</Button>
|
||||
</Typography>
|
||||
</Section>
|
||||
</MainLayout>
|
||||
)
|
||||
}
|
@ -3,7 +3,7 @@ import type { Meta, StoryObj } from "@storybook/react"
|
||||
import { Footer as FooterComponent } from "./Footer"
|
||||
|
||||
const meta = {
|
||||
title: "User Interface/Footer",
|
||||
title: "Layout/Footer",
|
||||
component: FooterComponent,
|
||||
} satisfies Meta<typeof FooterComponent>
|
||||
|
@ -1,7 +1,7 @@
|
||||
import { useTranslations } from "next-intl"
|
||||
|
||||
import { GIT_REPO_LINK } from "@repo/utils/constants"
|
||||
import { Link } from "../design/Link/Link"
|
||||
import { Link } from "../../Design/Link/Link"
|
||||
|
||||
export interface FooterProps {
|
||||
version: string
|
||||
@ -13,12 +13,12 @@ export const Footer: React.FC<FooterProps> = (props) => {
|
||||
const t = useTranslations()
|
||||
|
||||
return (
|
||||
<footer className="bg-background dark:bg-background-dark border-gray-darker dark:border-gray-darker-dark flex h-20 flex-col items-center justify-center border-t-2 px-6 py-2 text-lg">
|
||||
<footer className="bg-background dark:bg-background-dark border-gray-darker dark:border-gray-darker-dark flex flex-col items-center justify-center border-t-2 px-6 py-2 text-lg">
|
||||
<p>
|
||||
<Link href="https://theoludwig.fr" target="_blank" isExternal={false}>
|
||||
Théo LUDWIG
|
||||
</Link>{" "}
|
||||
| {t("meta.all-rights-reserved")}
|
||||
| {t("footer.all-rights-reserved")}
|
||||
</p>
|
||||
|
||||
<p>
|
@ -3,7 +3,7 @@ import type { Meta, StoryObj } from "@storybook/react"
|
||||
import { Header as HeaderComponent } from "./Header"
|
||||
|
||||
const meta = {
|
||||
title: "User Interface/Header",
|
||||
title: "Layout/Header",
|
||||
component: HeaderComponent,
|
||||
} satisfies Meta<typeof HeaderComponent>
|
||||
|
@ -5,7 +5,7 @@ import { SwitchTheme } from "./SwitchTheme"
|
||||
|
||||
export const Header: React.FC = () => {
|
||||
return (
|
||||
<header className="bg-background dark:bg-background-dark border-gray-darker dark:border-gray-darker-dark sticky top-0 z-50 flex h-16 flex-col items-center justify-center gap-4 border-b-2 px-4 py-2">
|
||||
<header className="bg-background dark:bg-background-dark border-gray-darker dark:border-gray-darker-dark sticky top-0 z-50 flex flex-col items-center justify-center gap-4 border-b-2 px-4 py-2">
|
||||
<div className="flex w-full items-center justify-between">
|
||||
<Locales />
|
||||
<SwitchTheme />
|
@ -2,13 +2,18 @@
|
||||
|
||||
import { classNames } from "@repo/config-tailwind/classNames"
|
||||
import { useIsMounted } from "@repo/react-hooks/useIsMounted"
|
||||
import { ThemeProvider as NextThemeProvider, useTheme } from "next-themes"
|
||||
import {
|
||||
ThemeProvider as NextThemeProvider,
|
||||
useTheme as useNextTheme,
|
||||
} from "next-themes"
|
||||
|
||||
export const THEMES = ["light", "dark"] as const
|
||||
export type Theme = (typeof THEMES)[number]
|
||||
export const THEME_DEFAULT = "light" as Theme
|
||||
export const THEME_DEFAULT = "dark" as Theme
|
||||
|
||||
export const ThemeProvider: React.FC<React.PropsWithChildren> = (props) => {
|
||||
export interface ThemeProviderProps extends React.PropsWithChildren {}
|
||||
|
||||
export const ThemeProvider: React.FC<ThemeProviderProps> = (props) => {
|
||||
const { children } = props
|
||||
|
||||
return (
|
||||
@ -22,19 +27,35 @@ export const ThemeProvider: React.FC<React.PropsWithChildren> = (props) => {
|
||||
)
|
||||
}
|
||||
|
||||
export const SwitchTheme: React.FC = () => {
|
||||
const { setTheme, theme: themeData } = useTheme()
|
||||
export interface UseThemeOutput {
|
||||
theme: Theme
|
||||
toggleTheme: () => void
|
||||
}
|
||||
|
||||
export const useTheme = (): UseThemeOutput => {
|
||||
const { setTheme, theme: themeData } = useNextTheme()
|
||||
const { isMounted } = useIsMounted()
|
||||
|
||||
const theme = isMounted ? (themeData as Theme) : THEME_DEFAULT
|
||||
|
||||
const handleClick = (): void => {
|
||||
const toggleTheme: UseThemeOutput["toggleTheme"] = () => {
|
||||
const newTheme = theme === "dark" ? "light" : "dark"
|
||||
setTheme(newTheme)
|
||||
}
|
||||
|
||||
return {
|
||||
theme,
|
||||
toggleTheme,
|
||||
}
|
||||
}
|
||||
|
||||
export interface SwitchThemeProps {}
|
||||
|
||||
export const SwitchTheme: React.FC<SwitchThemeProps> = () => {
|
||||
const { theme, toggleTheme } = useTheme()
|
||||
|
||||
return (
|
||||
<div className="flex items-center justify-center" onClick={handleClick}>
|
||||
<div className="flex items-center justify-center" onClick={toggleTheme}>
|
||||
<div className="relative inline-block cursor-pointer touch-pan-x select-none border-0 bg-transparent p-0">
|
||||
<div className="h-[24px] w-[50px] rounded-[30px] bg-[#4d4d4d] p-0 text-white transition-all duration-200 ease-in-out">
|
||||
<div
|
24
packages/ui/src/Layout/MainLayout/MainLayout.tsx
Normal file
24
packages/ui/src/Layout/MainLayout/MainLayout.tsx
Normal file
@ -0,0 +1,24 @@
|
||||
import { classNames } from "@repo/config-tailwind/classNames"
|
||||
|
||||
export interface MainLayoutProps
|
||||
extends React.ComponentPropsWithoutRef<"main"> {
|
||||
className?: string
|
||||
center?: boolean
|
||||
}
|
||||
|
||||
export const MainLayout: React.FC<MainLayoutProps> = (props) => {
|
||||
const { className, center = false, ...rest } = props
|
||||
|
||||
return (
|
||||
<main
|
||||
className={classNames(
|
||||
"min-h-[calc(100vh-188px)] md:mx-auto md:max-w-4xl lg:max-w-7xl",
|
||||
{
|
||||
"flex flex-col items-center justify-center text-center": center,
|
||||
},
|
||||
className,
|
||||
)}
|
||||
{...rest}
|
||||
/>
|
||||
)
|
||||
}
|
29
packages/ui/src/Layout/Section/Section.tsx
Normal file
29
packages/ui/src/Layout/Section/Section.tsx
Normal file
@ -0,0 +1,29 @@
|
||||
import { classNames } from "@repo/config-tailwind/classNames"
|
||||
|
||||
export interface SectionProps
|
||||
extends React.ComponentPropsWithoutRef<"section"> {
|
||||
verticalSpacing?: boolean
|
||||
horizontalSpacing?: boolean
|
||||
}
|
||||
|
||||
export const Section: React.FC<SectionProps> = (props) => {
|
||||
const {
|
||||
className,
|
||||
verticalSpacing = false,
|
||||
horizontalSpacing = false,
|
||||
...rest
|
||||
} = props
|
||||
|
||||
return (
|
||||
<section
|
||||
className={classNames(
|
||||
{
|
||||
"my-12": verticalSpacing,
|
||||
"mx-6": horizontalSpacing,
|
||||
},
|
||||
className,
|
||||
)}
|
||||
{...rest}
|
||||
/>
|
||||
)
|
||||
}
|
@ -1,20 +0,0 @@
|
||||
import { classNames } from "@repo/config-tailwind/classNames"
|
||||
|
||||
export interface MainLayoutProps extends React.PropsWithChildren {
|
||||
className?: string
|
||||
}
|
||||
|
||||
export const MainLayout: React.FC<MainLayoutProps> = (props) => {
|
||||
const { children, className } = props
|
||||
|
||||
return (
|
||||
<main
|
||||
className={classNames(
|
||||
"flex min-h-[calc(100vh-144px)] flex-col p-6",
|
||||
className,
|
||||
)}
|
||||
>
|
||||
{children}
|
||||
</main>
|
||||
)
|
||||
}
|
@ -1,5 +1,5 @@
|
||||
/**
|
||||
* Returns a date as a string value in ISO format (without time information).
|
||||
* Returns a date as a string value in ISO 8601 format (without time information).
|
||||
*
|
||||
* @param date
|
||||
* @returns
|
||||
|
@ -1,3 +1,9 @@
|
||||
/**
|
||||
* Capitalize the first letter of a string.
|
||||
* @param string
|
||||
* @returns
|
||||
* @example capitalize("hello, world!") // "Hello, world!"
|
||||
*/
|
||||
export const capitalize = (string: string): string => {
|
||||
return string.charAt(0).toUpperCase() + string.slice(1)
|
||||
}
|
||||
|
@ -1,8 +1,8 @@
|
||||
"use client"
|
||||
|
||||
import { Button } from "@repo/ui/design/Button"
|
||||
import { Link } from "@repo/ui/design/Link"
|
||||
import { Typography } from "@repo/ui/design/Typography"
|
||||
import { Button } from "@repo/ui/Design/Button"
|
||||
import { Link } from "@repo/ui/Design/Link"
|
||||
import { Typography } from "@repo/ui/Design/Typography"
|
||||
import { useState } from "react"
|
||||
import {
|
||||
fromLocaleToWikipediaLocale,
|
||||
@ -14,6 +14,7 @@ export const WikipediaClient: React.FC = () => {
|
||||
const [isLoading, setIsLoading] = useState(false)
|
||||
|
||||
const localeWikipedia = fromLocaleToWikipediaLocale("en-US")
|
||||
const wikipediaLink = getWikipediaLink(localeWikipedia)
|
||||
|
||||
const handleClick: React.MouseEventHandler<HTMLButtonElement> = async () => {
|
||||
setIsLoading(true)
|
||||
@ -54,9 +55,8 @@ export const WikipediaClient: React.FC = () => {
|
||||
Wikipedia
|
||||
</Button>
|
||||
<Typography variant="text1" as="p">
|
||||
<span>Using: </span>
|
||||
<Link href={getWikipediaLink(localeWikipedia)} target="_blank">
|
||||
{getWikipediaLink(localeWikipedia).replace("https://", "")}
|
||||
<Link href={wikipediaLink} target="_blank">
|
||||
{wikipediaLink.replace("https://", "")}
|
||||
</Link>
|
||||
</Typography>
|
||||
</section>
|
||||
|
1583
pnpm-lock.yaml
generated
1583
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
@ -16,12 +16,13 @@ catalog:
|
||||
"react-icons": "5.2.1"
|
||||
"@types/react": "18.3.3"
|
||||
"@types/react-dom": "18.3.0"
|
||||
"sharp": "0.33.4"
|
||||
|
||||
# TypeScript
|
||||
"typescript": "5.5.4"
|
||||
"@total-typescript/ts-reset": "0.5.1"
|
||||
"@types/node": "22.0.0"
|
||||
"tsx": "4.16.2"
|
||||
"tsx": "4.16.3"
|
||||
|
||||
# ESLint
|
||||
"@typescript-eslint/eslint-plugin": "7.18.0"
|
||||
@ -47,7 +48,7 @@ catalog:
|
||||
"@storybook/react": "8.2.6"
|
||||
"@storybook/test": "8.2.6"
|
||||
"@storybook/test-runner": "0.19.1"
|
||||
"chromatic": "11.5.6"
|
||||
"chromatic": "11.6.0"
|
||||
"http-server": "14.1.1"
|
||||
"storybook": "8.2.6"
|
||||
"storybook-dark-mode": "4.0.2"
|
||||
@ -57,10 +58,10 @@ catalog:
|
||||
"@playwright/test": "1.45.3"
|
||||
"axe-playwright": "2.0.1"
|
||||
"start-server-and-test": "2.0.5"
|
||||
"@vitest/browser": "2.0.4"
|
||||
"@vitest/coverage-istanbul": "2.0.4"
|
||||
"@vitest/ui": "2.0.4"
|
||||
"vitest": "2.0.4"
|
||||
"@vitest/browser": "2.0.5"
|
||||
"@vitest/coverage-istanbul": "2.0.5"
|
||||
"@vitest/ui": "2.0.5"
|
||||
"vitest": "2.0.5"
|
||||
"@testing-library/react": "16.0.0"
|
||||
|
||||
# CSS
|
||||
|
Reference in New Issue
Block a user