Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improve Dark Mode support #159

Merged
merged 7 commits into from
Oct 31, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
37 changes: 37 additions & 0 deletions apps/_docs/content/docs/dark-mode.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
---
title: Dark Mode
description: How to use dark mode in the design system.
---

next-forge comes with built-in dark mode support through the combination of [Tailwind CSS](https://tailwindcss.com/docs/dark-mode) and [next-themes](https://github.com/pacocoursey/next-themes).

## Implementation

The dark mode implementation uses Tailwind's `darkMode: 'class'` strategy, which toggles dark mode by adding a `dark` class to the `html` element. This approach provides better control over dark mode and prevents flash of incorrect theme.

The `next-themes` provider is already configured in the application, handling theme persistence and system preference detection automatically.

To allow the user to change theme, you can use the `ModeToggle` component.

## Caveats

Currently, it's not possible to change the Clerk theme to match the exact theme of the app. This is because Clerk's Theme doesn't accept custom CSS variables. We'd like to be able to add the following in the future:

```jsx title="packages/design-system/providers/clerk.tsx"
const variables: Theme['variables'] = {
// ...

colorBackground: 'hsl(var(--background))', // [!code ++]
colorPrimary: 'hsl(var(--primary))', // [!code ++]
colorDanger: 'hsl(var(--destructive))', // [!code ++]
colorInputBackground: 'hsl(var(--transparent))', // [!code ++]
colorInputText: 'hsl(var(--text-foreground))', // [!code ++]
colorNeutral: 'hsl(var(--neutral))', // [!code ++]
colorShimmer: 'hsl(var(--primary) / 10%)', // [!code ++]
colorSuccess: 'hsl(var(--success))', // [!code ++]
colorText: 'hsl(var(--text-foreground))', // [!code ++]
colorTextOnPrimaryBackground: 'hsl(var(--text-foreground))', // [!code ++]
colorTextSecondary: 'hsl(var(--text-muted-foreground))', // [!code ++]
colorWarning: 'hsl(var(--warning))', // [!code ++]
};
```
1 change: 1 addition & 0 deletions apps/_docs/content/docs/meta.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
"analytics",
"authentication",
"blog",
"dark-mode",
"debugging",
"email",
"flags",
Expand Down
7 changes: 5 additions & 2 deletions apps/app/app/(authenticated)/components/sidebar.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
'use client';

import { OrganizationSwitcher, UserButton } from '@clerk/nextjs';
import { ModeToggle } from '@repo/design-system/components/mode-toggle';
import {
Collapsible,
CollapsibleContent,
Expand Down Expand Up @@ -313,16 +314,18 @@ export const GlobalSidebar = ({ children }: GlobalSidebarProperties) => {
</SidebarContent>
<SidebarFooter>
<SidebarMenu>
<SidebarMenuItem>
<SidebarMenuItem className="flex items-center gap-2">
<UserButton
showName
appearance={{
elements: {
rootBox: 'flex',
rootBox: 'flex overflow-hidden',
userButtonBox: 'flex-row-reverse',
userButtonOuterIdentifier: 'truncate pl-0',
},
}}
/>
<ModeToggle />
</SidebarMenuItem>
</SidebarMenu>
</SidebarFooter>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { SignIn } from '@clerk/nextjs';
import { ModeToggle } from '@repo/design-system/components/mode-toggle';
import { createMetadata } from '@repo/design-system/lib/metadata';
import type { Metadata } from 'next';

Expand All @@ -8,7 +9,10 @@ const description = 'Sign in to your account.';
export const metadata: Metadata = createMetadata({ title, description });

const SignInPage = () => (
<div className="flex min-h-dvh w-full flex-col items-center justify-center bg-secondary">
<div className="flex min-h-dvh w-full flex-col items-center justify-center bg-secondary dark:bg-background">
<div className="absolute top-4 right-4">
<ModeToggle />
</div>
<SignIn />
</div>
);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { SignUp } from '@clerk/nextjs';
import { ModeToggle } from '@repo/design-system/components/mode-toggle';
import { createMetadata } from '@repo/design-system/lib/metadata';
import type { Metadata } from 'next';

Expand All @@ -8,7 +9,10 @@ const description = 'Sign Up to your account.';
export const metadata: Metadata = createMetadata({ title, description });

const SignUpPage = () => (
<div className="flex min-h-dvh w-full flex-col items-center justify-center bg-secondary">
<div className="flex min-h-dvh w-full flex-col items-center justify-center bg-secondary dark:bg-background">
<div className="absolute top-4 right-4">
<ModeToggle />
</div>
<SignUp />
</div>
);
Expand Down
38 changes: 19 additions & 19 deletions apps/app/app/layout.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import '@repo/design-system/styles/globals.css';
import { ClerkProvider } from '@clerk/nextjs';
import { Toaster } from '@repo/design-system/components/ui/sonner';
import { TooltipProvider } from '@repo/design-system/components/ui/tooltip';
import { cn } from '@repo/design-system/lib/utils';
import { DesignSystemProvider } from '@repo/design-system/providers';
import { ClerkProvider } from '@repo/design-system/providers/clerk';
import { Analytics } from '@vercel/analytics/react';
import { GeistMono } from 'geist/font/mono';
import { GeistSans } from 'geist/font/sans';
Expand All @@ -14,25 +14,25 @@ type RootLayoutProperties = {
};

const RootLayout = ({ children }: RootLayoutProperties) => (
<ClerkProvider>
<html
lang="en"
className={cn(
GeistSans.variable,
GeistMono.variable,
'touch-manipulation font-sans antialiased'
)}
suppressHydrationWarning
>
<body>
<DesignSystemProvider>
<html
lang="en"
className={cn(
GeistSans.variable,
GeistMono.variable,
'touch-manipulation font-sans antialiased'
)}
suppressHydrationWarning
>
<body>
<DesignSystemProvider>
<ClerkProvider>
<TooltipProvider>{children}</TooltipProvider>
</DesignSystemProvider>
<Toaster />
<Analytics />
</body>
</html>
</ClerkProvider>
<Toaster />
<Analytics />
</ClerkProvider>
</DesignSystemProvider>
</body>
</html>
);

export default RootLayout;
120 changes: 62 additions & 58 deletions apps/web/app/components/footer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -44,68 +44,72 @@ export const Footer = () => {
];

return (
<div className="w-full bg-foreground py-20 text-background lg:py-40">
<div className="container mx-auto">
<div className="grid items-center gap-10 lg:grid-cols-2">
<div className="flex flex-col items-start gap-8">
<div className="flex flex-col gap-2">
<h2 className="max-w-xl text-left font-regular text-3xl tracking-tighter md:text-5xl">
next-forge
</h2>
<p className="max-w-lg text-left text-background/75 text-lg leading-relaxed tracking-tight">
This is the start of something new.
</p>
<section className="dark border-foreground/10 border-t">
<div className="w-full bg-background py-20 text-foreground lg:py-40">
<div className="container mx-auto">
<div className="grid items-center gap-10 lg:grid-cols-2">
<div className="flex flex-col items-start gap-8">
<div className="flex flex-col gap-2">
<h2 className="max-w-xl text-left font-regular text-3xl tracking-tighter md:text-5xl">
next-forge
</h2>
<p className="max-w-lg text-left text-foreground/75 text-lg leading-relaxed tracking-tight">
This is the start of something new.
</p>
</div>
<Status />
</div>
<Status />
</div>
<div className="grid items-start gap-10 lg:grid-cols-3">
{navigationItems.map((item) => (
<div
key={item.title}
className="flex flex-col items-start gap-1 text-base"
>
<div className="flex flex-col gap-2">
{item.href ? (
<Link
href={item.href}
className="flex items-center justify-between"
target={item.href.includes('http') ? '_blank' : undefined}
rel={
item.href.includes('http')
? 'noopener noreferrer'
: undefined
}
>
<span className="text-xl">{item.title}</span>
</Link>
) : (
<p className="text-xl">{item.title}</p>
)}
{item.items?.map((subItem) => (
<Link
key={subItem.title}
href={subItem.href}
className="flex items-center justify-between"
target={
subItem.href.includes('http') ? '_blank' : undefined
}
rel={
subItem.href.includes('http')
? 'noopener noreferrer'
: undefined
}
>
<span className="text-background/75">
{subItem.title}
</span>
</Link>
))}
<div className="grid items-start gap-10 lg:grid-cols-3">
{navigationItems.map((item) => (
<div
key={item.title}
className="flex flex-col items-start gap-1 text-base"
>
<div className="flex flex-col gap-2">
{item.href ? (
<Link
href={item.href}
className="flex items-center justify-between"
target={
item.href.includes('http') ? '_blank' : undefined
}
rel={
item.href.includes('http')
? 'noopener noreferrer'
: undefined
}
>
<span className="text-xl">{item.title}</span>
</Link>
) : (
<p className="text-xl">{item.title}</p>
)}
{item.items?.map((subItem) => (
<Link
key={subItem.title}
href={subItem.href}
className="flex items-center justify-between"
target={
subItem.href.includes('http') ? '_blank' : undefined
}
rel={
subItem.href.includes('http')
? 'noopener noreferrer'
: undefined
}
>
<span className="text-foreground/75">
{subItem.title}
</span>
</Link>
))}
</div>
</div>
</div>
))}
))}
</div>
</div>
</div>
</div>
</div>
</section>
);
};
2 changes: 2 additions & 0 deletions apps/web/app/components/header/index.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
'use client';

import { ModeToggle } from '@repo/design-system/components/mode-toggle';
import { Button } from '@repo/design-system/components/ui/button';
import {
NavigationMenu,
Expand Down Expand Up @@ -130,6 +131,7 @@ export const Header = () => {
<Link href="/contact">Contact us</Link>
</Button>
<div className="hidden border-r md:inline" />
<ModeToggle />
<Button variant="outline" asChild>
<Link href={`${appUrl}/sign-in`}>Sign in</Link>
</Button>
Expand Down
45 changes: 45 additions & 0 deletions packages/design-system/components/mode-toggle.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
'use client';

import { MoonIcon, SunIcon } from 'lucide-react';
import { useTheme } from 'next-themes';
import type { FC } from 'react';
import { Button } from '../components/ui/button';
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuTrigger,
} from '../components/ui/dropdown-menu';

export const ModeToggle: FC = () => {
const { setTheme } = useTheme();

const themes = [
{ onClick: () => setTheme('light'), children: 'Light' },
{ onClick: () => setTheme('dark'), children: 'Dark' },
{ onClick: () => setTheme('system'), children: 'System' },
];

return (
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button
variant="outline"
size="icon"
className="shrink-0 text-foreground"
>
<SunIcon className="dark:-rotate-90 h-[1.2rem] w-[1.2rem] rotate-0 scale-100 transition-all dark:scale-0" />
<MoonIcon className="absolute h-[1.2rem] w-[1.2rem] rotate-90 scale-0 transition-all dark:rotate-0 dark:scale-100" />
<span className="sr-only">Toggle theme</span>
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent>
{themes.map(({ onClick, children }) => (
<DropdownMenuItem key={children} onClick={onClick}>
{children}
</DropdownMenuItem>
))}
</DropdownMenuContent>
</DropdownMenu>
);
};
4 changes: 1 addition & 3 deletions packages/design-system/lib/tailwind.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,4 @@
import resolveConfig from 'tailwindcss/resolveConfig';
import tailwindConfig from '../tailwind.config';

const tailwind = resolveConfig(tailwindConfig);

export default tailwind;
export const tailwind = resolveConfig(tailwindConfig);
3 changes: 3 additions & 0 deletions packages/design-system/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
"version": "0.0.0",
"private": true,
"dependencies": {
"@clerk/nextjs": "^6.0.2",
"@clerk/themes": "^2.1.39",
"@hookform/resolvers": "^3.9.1",
"@logtail/next": "^0.1.5",
"@next/third-parties": "^15.0.2",
Expand Down Expand Up @@ -71,6 +73,7 @@
"zod": "^3.23.8"
},
"devDependencies": {
"@clerk/types": "^4.28.0",
"@repo/typescript-config": "workspace:*",
"@types/node": "^22.8.5",
"@types/react": "^18.3.12",
Expand Down
Loading