Skip to content
Open
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
116 changes: 116 additions & 0 deletions docs/features/dark-mode.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
---
id: dark-mode
title: Dark Mode
description: Dark mode support with persistent user preference
---

# Dark Mode

CodeHarborHub includes full dark mode support with persistent user preferences. Your theme choice is automatically saved and remembered across sessions.

## Features

✨ **Automatic Theme Detection**: Respects your system preference if you haven't set a preference in CodeHarborHub

🌓 **Manual Theme Control**: Use the theme toggle button in the navigation bar to switch between light and dark modes

💾 **Persistent Preferences**: Your theme choice is saved in browser localStorage and restored on your next visit

⚡ **Smooth Transitions**: Color transitions are animated for a pleasant visual experience

🎨 **Complete Coverage**: All components and pages support dark mode

## Using Dark Mode

### Switching Themes

1. Look for the theme toggle button in the top-right corner of the navigation bar
2. Click the button to switch between light and dark modes
3. Your preference is automatically saved

### System Preference

If you haven't manually set a theme:
- CodeHarborHub will check your system-wide dark mode preference
- If your system is set to dark mode, the site will automatically display in dark mode
- You can override this anytime by using the theme toggle button

## For Developers

### Using the Color Mode Hook

If you're developing components for CodeHarborHub, you can use the `useColorMode` hook to access the current theme:

```jsx
import useColorMode from '@site/src/hooks/useColorMode';

export default function MyComponent() {
const { colorMode, isDark, toggleColorMode } = useColorMode();

return (
<div>
<p>Current theme: {colorMode}</p>
<button onClick={toggleColorMode}>
Switch to {isDark ? 'light' : 'dark'} mode
</button>
</div>
);
}
```

### CSS Variables

Dark mode is applied using the `[data-theme="dark"]` attribute. Use these CSS variables for theming:

**Light Mode** (`:root`):
```css
--ifm-color-primary: #2e93e2;
--ifm-bg-color: #f8f9fa;
--ifm-text-color: #000;
```

**Dark Mode** (`[data-theme="dark"]`):
```css
--ifm-color-primary: #48bf84;
--ifm-bg-color: #1a202c;
--ifm-text-color: #fff;
```

### Creating Dark-Mode Aware Components

```jsx
import styles from './MyComponent.module.css';

export default function MyComponent() {
return <div className={styles.card}>Content</div>;
}
```

```css
/* MyComponent.module.css */
.card {
background: white;
color: black;
}

[data-theme="dark"] .card {
background: #1a202c;
color: white;
}
```

## Browser Compatibility

Dark mode works on all modern browsers that support CSS custom properties and localStorage:
- Chrome/Edge 49+
- Firefox 31+
- Safari 9.1+
- Opera 36+

## Storage

Your theme preference is stored in the browser's localStorage under the key `theme`. You can manually clear this to reset to system preference:

```javascript
localStorage.removeItem('theme');
```
5 changes: 5 additions & 0 deletions docusaurus.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,11 @@ const config = {
themeConfig:
/** @type {import('@docusaurus/preset-classic').ThemeConfig} */
({
colorMode: {
defaultMode: 'light',
disableSwitch: false,
respectPrefersColorScheme: true,
},
image: "img/codeharborhub-social-card.jpg",
announcementBar: {
id: "announcementBar",
Expand Down
42 changes: 42 additions & 0 deletions src/components/ThemeToggle.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import React from 'react';
import useColorMode from '@site/src/hooks/useColorMode';
import styles from './ThemeToggle.module.css';

export default function ThemeToggle() {
const { colorMode, toggleColorMode, isDark } = useColorMode();

return (
<button
className={styles.themeToggle}
onClick={toggleColorMode}
aria-label={`Switch to ${isDark ? 'light' : 'dark'} mode`}
title={`Current theme: ${colorMode}`}
>
{isDark ? (
<svg
className={styles.icon}
fill="currentColor"
viewBox="0 0 20 20"
xmlns="http://www.w3.org/2000/svg"
aria-hidden="true"
>
<path d="M17.293 13.293A8 8 0 016.707 2.707a8.001 8.001 0 1010.586 10.586z" />
</svg>
) : (
<svg
className={styles.icon}
fill="currentColor"
viewBox="0 0 20 20"
xmlns="http://www.w3.org/2000/svg"
aria-hidden="true"
>
<path
fillRule="evenodd"
d="M10 2a1 1 0 011 1v1a1 1 0 11-2 0V3a1 1 0 011-1zm4.293 1.293a1 1 0 011.414 0l.707.707a1 1 0 11-1.414 1.414l-.707-.707a1 1 0 010-1.414zm2.828 2.828a1 1 0 011.414 0l.707.707a1 1 0 11-1.414 1.414l-.707-.707a1 1 0 010-1.414zm1.414 5.657a1 1 0 110-2h1a1 1 0 110 2h-1zm-16 0a1 1 0 110-2h1a1 1 0 110 2h-1zm14.142 1.142a1 1 0 011.414 1.414l-.707.707a1 1 0 11-1.414-1.414l.707-.707zm2.828 2.828a1 1 0 011.414 1.414l-.707.707a1 1 0 11-1.414-1.414l.707-.707zM10 15a1 1 0 011 1v1a1 1 0 11-2 0v-1a1 1 0 011-1zm-4.293-1.293a1 1 0 011.414 0l.707.707a1 1 0 11-1.414 1.414l-.707-.707a1 1 0 010-1.414zM2.707 4.707a1 1 0 010-1.414l.707-.707a1 1 0 011.414 1.414l-.707.707a1 1 0 01-1.414 0z"
clipRule="evenodd"
/>
</svg>
)}
</button>
);
}
49 changes: 49 additions & 0 deletions src/components/ThemeToggle.module.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
.themeToggle {
display: inline-flex;
align-items: center;
justify-content: center;
width: 36px;
height: 36px;
padding: 0;
border: none;
border-radius: 6px;
background: transparent;
color: var(--ifm-text-color);
cursor: pointer;
transition: all 0.2s ease;
font-family: inherit;
}

.themeToggle:hover {
background: var(--ifm-color-emphasis-200);
}

.themeToggle:focus-visible {
outline: 2px solid var(--ifm-color-primary);
outline-offset: 2px;
}

.themeToggle:active {
transform: scale(0.95);
}

.icon {
width: 20px;
height: 20px;
display: block;
}

@media (prefers-reduced-motion: no-preference) {
.themeToggle {
transition: background 0.2s cubic-bezier(0.4, 0, 0.2, 1),
color 0.2s cubic-bezier(0.4, 0, 0.2, 1);
}

.icon {
transition: transform 0.3s cubic-bezier(0.4, 0, 0.2, 1);
}

.themeToggle:hover .icon {
transform: rotate(15deg);
}
}
13 changes: 13 additions & 0 deletions src/css/custom.css
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,19 @@ body {
background-image: url("/landing/grid-dark.svg");
}

/* Smooth theme transitions */
@media (prefers-reduced-motion: no-preference) {
html {
color-scheme: light dark;
transition: background-color 0.3s ease, color 0.3s ease;
}

body,
:root {
transition: background-color 0.3s ease, color 0.3s ease, border-color 0.3s ease;
}
}

.header-github-link::before {
content: "";
width: 24px;
Expand Down
25 changes: 25 additions & 0 deletions src/hooks/useColorMode.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import { useContext } from 'react';
import { ColorModeContext } from '@docusaurus/theme-common';

export function useColorMode() {
const { colorMode, setColorMode } = useContext(ColorModeContext);

const toggleColorMode = () => {
setColorMode(colorMode === 'light' ? 'dark' : 'light');
};

const setLight = () => setColorMode('light');
const setDark = () => setColorMode('dark');

return {
colorMode,
setColorMode,
toggleColorMode,
setLight,
setDark,
isDark: colorMode === 'dark',
isLight: colorMode === 'light',
};
}

export default useColorMode;
Loading