Skip to content

Commit 8431d01

Browse files
authored
Merge pull request #4 from designcise/feat/typescript-support
feat: add typescript support
2 parents dcec742 + 079fe85 commit 8431d01

37 files changed

+4361
-7909
lines changed

.babelrc

Lines changed: 0 additions & 22 deletions
This file was deleted.

.prettierrc

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
{
22
"semi": false,
33
"singleQuote": true,
4-
"printWidth": 100
4+
"bracketSameLine": true,
5+
"printWidth": 100,
6+
"arrowParens": "avoid"
57
}

README.md

Lines changed: 26 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@ You can then [use different CSS selectors to create styles for dark/light themes
1313
## Features
1414

1515
- Easy implementation with just _two_ lines of code
16+
- TypeScript Support
17+
- Types are automatically loaded, whenever applicable
1618
- No flicker on page load
1719
- Toggle between `light`, `dark` and `auto` modes
1820
- Automatically choose color based on `prefers-color-scheme` when in "`auto`" mode
@@ -54,16 +56,13 @@ At a bare minimum you need to do the following:
5456
import { ThemeProvider } from '@designcise/next-theme-toggle';
5557
import { themes } from '@designcise/next-theme-toggle/server';
5658

57-
// 1: specify key for storage
58-
const THEME_STORAGE_KEY = 'theme-preference'
59-
6059
export default async function RootLayout() {
61-
// 2: wrap components with `ThemeProvider` to pass theme props down to all components
62-
// 3: pass `storageKey` and (optional) `defaultTheme` to `ThemeProvider`
60+
// 1: wrap components with `ThemeProvider` to pass theme props down to all components
61+
// 2: optionally pass `storageKey` and `defaultTheme` to `ThemeProvider`
6362
return (
6463
<html>
6564
<body>
66-
<ThemeProvider storageKey={THEME_STORAGE_KEY} defaultTheme={themes.dark}>
65+
<ThemeProvider storageKey="user-pref" defaultTheme={themes.dark.type}>
6766
{children}
6867
</ThemeProvider>
6968
</body>
@@ -74,6 +73,8 @@ export default async function RootLayout() {
7473

7574
With this setup, the `ThemeProvider` component will automatically inject an inline script into DOM that takes care of avoiding flicker on initial page load.
7675

76+
> **NOTE**: If you don't specify a `storageKey` or `defaultTheme` prop on `ThemeProvider`, default value will be used for `storageKey` while absence of `defaultTheme` would mean that the theme is automatically determined based on `prefers-color-scheme`.
77+
7778
2. Create a button to toggle between light and dark theme:
7879

7980
```jsx
@@ -90,7 +91,7 @@ export default function ToggleThemeButton() {
9091
}
9192
```
9293

93-
You can also do this manually by using `theme`, `themes`, `colors` and `setTheme()`, for example, like so:
94+
You can also do this manually by using `theme`, `themes`, and `setTheme()`, for example, like so:
9495

9596
```jsx
9697
// components/ToggleThemeButton/index.jsx
@@ -100,10 +101,10 @@ import React, { useContext } from 'react'
100101
import { useTheme } from '@designcise/next-theme-toggle'
101102

102103
export default function ToggleThemeButton() {
103-
const { theme, themes, colors, setTheme } = useTheme()
104+
const { theme, themes, setTheme } = useTheme()
104105

105106
return (
106-
<button onClick={() => setTheme(theme === themes.dark ? colors.light : colors.dark)}>
107+
<button onClick={() => setTheme(theme.type === themes.dark.type ? themes.light : themes.dark)}>
107108
Toggle Theme
108109
</button>
109110
)
@@ -176,34 +177,32 @@ That's it! You should have light/dark theme toggle in your Next.js application.
176177

177178
You can pass the following props to `ThemeProvider`:
178179

179-
| Prop | Type | Description |
180-
|----------------|:--------------------------------------------:|:--------------------------------------------------------------------------------------:|
181-
| `children` | `React.ReactChild`&vert;`React.ReactChild[]` | Components to which the theme is passed down to via context. |
182-
| `storageKey` | String | Name of the key used for storage. |
183-
| `defaultTheme` | String | Default theme (`'light'`, `'dark'` or `auto`) to use on page load. Defaults to `auto`. |
180+
| Prop | Type | Description |
181+
|----------------|:--------------------------------------------:|:---------------------------------------------------------------------------------------:|
182+
| `children` | `React.ReactChild`&vert;`React.ReactChild[]` | Components to which the theme is passed down to via context. |
183+
| `storageKey` | String | Name of the key used for storage. Defaults to `DEFAULT_STORAGE_KEY` in `env.helper.ts`. |
184+
| `defaultTheme` | String | Default theme (`'light'`, `'dark'` or `auto`) to use on page load. Defaults to `auto`. |
184185

185186
### `useTheme()`
186187

187188
The `useTheme()` hook does not take any params; it returns the following:
188189

189-
| Return Value | Type | Description |
190-
|---------------|:--------:|:--------------------------------------------------------------------------------------------------------------------------------------------------------:|
191-
| `theme` | String | The active theme (`'light'`, `'dark'` or `'auto'`). |
192-
| `themes` | Object | Allowed themes (`{ light: 'light', dark: 'dark', auto: 'auto' }`). |
193-
| `color` | String | The active color (`light` or `dark`). |
194-
| `colors` | Object | Allowed colors (`{ light: 'light', dark: 'dark', auto: 'dark'&vert;'light' }`). `colors.auto` returns `dark` or `light` based on `prefers-color-scheme`. |
195-
| `setTheme` | Function | Setter to set new theme. |
196-
| `toggleTheme` | Function | Toggles the theme between `light` and `dark`. When toggling from `auto`, the opposite color to active color is automatically chosen. |
190+
| Return Value | Type | Description |
191+
|---------------|:--------:|:------------------------------------------------------------------------------------------------------------------------------------:|
192+
| `theme` | Object | The active theme (e.g. `{ type: 'light', color: 'light' }`). |
193+
| `themes` | Object | Allowed themes (e.g. `{ light: { type: 'light', color: 'light' }, dark: ..., auto: ... }`). |
194+
| `setTheme` | Function | Setter to set new theme. |
195+
| `toggleTheme` | Function | Toggles the theme between `light` and `dark`. When toggling from `auto`, the opposite color to active color is automatically chosen. |
197196

198197
### `themes`
199198

200199
An object, with the following properties:
201200

202-
| Property | Type | Value | Description |
203-
|----------|:------:|:---------:|:------------------------------------------------------------:|
204-
| `light` | String | `'light'` | Color value used for "light" theme. |
205-
| `dark` | String | `'dark'`. | Color value used for "dark" theme. |
206-
| `auto` | String | `'auto'`. | Auto-determine color scheme based on `prefers-color-scheme`. |
201+
| Property | Type | Value | Description |
202+
|----------|:------:|:------------------------------------------------:|:-----------------------------------------------------------:|
203+
| `light` | Object | `{ type: 'light', color: 'light' }` | Light theme. |
204+
| `dark` | Object | `{ type: 'dark', color: 'dark' }` | Dark theme. |
205+
| `auto` | Object | `{ type: 'auto', color: 'dark' &vert; 'light' }` | Auto-determine theme color based on `prefers-color-scheme`. |
207206

208207
> **NOTE**: The `themes` object can be used in both, client components and server components.
209208
@@ -244,32 +243,6 @@ $ yarn test
244243

245244
### Troubleshooting Common Issues
246245

247-
#### Tailwind not updating dark mode styling
248-
249-
This can happen when you have your CSS or SASS file in a sub-folder that is not listed in `content` array in the `tailwind.config.js`:
250-
251-
```js
252-
// ...
253-
content: [
254-
'./pages/**/*.{js,jsx}',
255-
'./components/**/*.{js,jsx}',
256-
'./app/**/*.{js,jsx}',
257-
'./src/**/*.{js,jsx}',
258-
],
259-
// ...
260-
```
261-
262-
To fix this, you can add the folder where your CSS or SASS file is located. For example:
263-
264-
```js
265-
// ...
266-
content: [
267-
// ...
268-
'./src/styles/**/*.css',
269-
],
270-
// ...
271-
```
272-
273246
#### `Warning: Extra attributes from the server: class,style` in Console
274247

275248
You can safely ignore this warning as it _only_ shows on dev build and _not_ in the production build. This happens because the injected inline script adds _additional_ `class` and `style` attributes to the `html` element which _do not_ originally exist on the server-side generated page, leading to a _mismatch_ in the server-side and client-side rendered page.

__tests__/ThemeProvider.test.jsx renamed to __tests__/ThemeProvider.test.tsx

Lines changed: 23 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,12 @@
1-
import React from 'react'
1+
// @ts-nocheck
22
import { render, screen, fireEvent } from '@testing-library/react'
33
import { ThemeProvider } from '../src/client'
44
import { mockLocalStorage, mockMatchMedia, mockPreferredColorScheme } from './mocks/device.mock'
55
import { read, write, clear } from '../src/adapter/storage.adapter'
66
import ThemeAutoToggle from './assets/ThemeAutoToggle'
77
import ThemeManualToggle from './assets/ThemeManualToggle'
88
import ThemeSwitcher from './assets/ThemeSwitcher'
9+
import { DEFAULT_STORAGE_KEY } from '../src/helper/env.helper'
910

1011
beforeAll(() => {
1112
mockLocalStorage()
@@ -18,7 +19,7 @@ beforeEach(() => {
1819
document.documentElement.removeAttribute('class')
1920
})
2021

21-
describe('provider', () => {
22+
describe('ThemeProvider', () => {
2223
test('should set storage key according to the specified value', () => {
2324
const storageKey = 'theme-test'
2425
const expectedTheme = 'light'
@@ -32,9 +33,21 @@ describe('provider', () => {
3233
expect(read(storageKey)).toEqual(expectedTheme)
3334
})
3435

36+
test('should use default storage key when none is specified value', () => {
37+
const expectedThemeType = 'light'
38+
39+
render(
40+
<ThemeProvider defaultTheme={expectedThemeType}>
41+
<ThemeAutoToggle />
42+
</ThemeProvider>,
43+
)
44+
45+
expect(read(DEFAULT_STORAGE_KEY)).toEqual(expectedThemeType)
46+
})
47+
3548
test.each(['light', 'dark'])(
3649
'should use the `defaultTheme` when nothing is stored in `localStorage`',
37-
(theme) => {
50+
theme => {
3851
const storageKey = 'test'
3952

4053
render(
@@ -51,7 +64,7 @@ describe('provider', () => {
5164

5265
test.each(['light', 'dark'])(
5366
'should auto-determine theme color when nothing is stored in `localStorage` and `defaultTheme` is set to "auto"',
54-
(color) => {
67+
color => {
5568
const storageKey = 'test'
5669
mockPreferredColorScheme(color)
5770

@@ -69,7 +82,7 @@ describe('provider', () => {
6982

7083
test.each(['light', 'dark'])(
7184
'should set `color-scheme` and `class` to "%s" theme color according to saved theme preference',
72-
(color) => {
85+
color => {
7386
const storageKey = 'test'
7487
write(storageKey, color)
7588

@@ -86,7 +99,7 @@ describe('provider', () => {
8699

87100
test.each(['light', 'dark', 'auto'])(
88101
'should use system resolved "%s" color and "auto" theme when no `defaultTheme` is provided and nothing is stored in `localStorage`',
89-
(color) => {
102+
color => {
90103
const storageKey = 'sys-resolved-theme'
91104
const prefColor = color === 'auto' ? 'dark' : color
92105

@@ -106,7 +119,7 @@ describe('provider', () => {
106119

107120
test.each(['light', 'dark'])(
108121
'should set theme color automatically based on user system preference',
109-
(sysPrefColor) => {
122+
sysPrefColor => {
110123
const storageKey = 'sys-resolved-theme'
111124
mockPreferredColorScheme(sysPrefColor)
112125

@@ -183,7 +196,7 @@ describe('provider', () => {
183196
},
184197
)
185198

186-
test.each(['light', 'dark'])('should switch from "auto" to "%s"', (theme) => {
199+
test.each(['light', 'dark'])('should switch from "auto" to "%s"', theme => {
187200
const storageKey = 'sys-resolved-theme'
188201
const oppositeTheme = theme === 'dark' ? 'light' : 'dark'
189202
mockPreferredColorScheme(oppositeTheme)
@@ -205,7 +218,7 @@ describe('provider', () => {
205218
expect(document.documentElement.style.colorScheme).toBe(theme)
206219
})
207220

208-
test.each(['light', 'dark'])('should switch from "%s" to "auto"', (theme) => {
221+
test.each(['light', 'dark'])('should switch from "%s" to "auto"', theme => {
209222
const storageKey = 'sys-resolved-theme'
210223
const oppositeTheme = theme === 'dark' ? 'light' : 'dark'
211224
mockPreferredColorScheme(oppositeTheme)
@@ -231,7 +244,7 @@ describe('provider', () => {
231244
const storageKey = 'sys-resolved-theme'
232245

233246
render(
234-
<ThemeProvider storageKey={storageKey} theme="auto">
247+
<ThemeProvider storageKey={storageKey} defaultTheme="auto">
235248
<ThemeSwitcher />
236249
</ThemeProvider>,
237250
)

__tests__/assets/ThemeAutoColor.jsx

Lines changed: 0 additions & 13 deletions
This file was deleted.

__tests__/assets/ThemeAutoColor.tsx

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
import { useTheme } from '../../src/client'
2+
3+
export default function ThemeAutoColor() {
4+
const { theme, themes } = useTheme()
5+
6+
return (
7+
<>
8+
<div>Active Theme: {theme.type}</div>
9+
<div>Auto-determined Color: {themes.auto.color}</div>
10+
</>
11+
)
12+
}

__tests__/assets/ThemeAutoToggle.jsx renamed to __tests__/assets/ThemeAutoToggle.tsx

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
'use client'
22

3-
import React from 'react'
43
import { useTheme } from '../../src/client'
54

65
export default function ToggleThemeButton() {

__tests__/assets/ThemeManualToggle.jsx

Lines changed: 0 additions & 14 deletions
This file was deleted.
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
'use client'
2+
3+
import { useTheme } from '../../src/client'
4+
5+
export default function ToggleThemeButton() {
6+
const { theme, themes, setTheme } = useTheme()
7+
8+
return (
9+
<button onClick={() => setTheme(theme.type === themes.dark.type ? themes.light : themes.dark)}>
10+
Toggle Theme
11+
</button>
12+
)
13+
}

__tests__/assets/ThemeSwitcher.jsx renamed to __tests__/assets/ThemeSwitcher.tsx

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,12 @@
1-
import React from 'react'
21
import { useTheme } from '../../src/client'
32

43
export default function ThemeSwitcher() {
5-
const { theme, themes, color, setTheme } = useTheme()
4+
const { theme, themes, setTheme } = useTheme()
65

76
return (
87
<>
9-
<div>Active Theme: {theme}</div>
10-
<div>Active Color: {color}</div>
8+
<div>Active Theme: {theme.type}</div>
9+
<div>Active Color: {theme.color}</div>
1110
<button onClick={() => setTheme(themes.light)}>Light Theme</button>
1211
<button onClick={() => setTheme(themes.dark)}>Dark Theme</button>
1312
<button onClick={() => setTheme(themes.auto)}>Auto Theme</button>

__tests__/mocks/device.mock.js renamed to __tests__/mocks/device.mock.ts

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
1+
// @ts-nocheck
12
export function mockMatchMedia() {
23
Object.defineProperty(window, 'matchMedia', {
34
writable: true,
4-
value: jest.fn().mockImplementation((query) => ({
5+
value: jest.fn().mockImplementation(query => ({
56
matches: false,
67
media: query,
78
onchange: null,
@@ -14,11 +15,11 @@ export function mockMatchMedia() {
1415
})
1516
}
1617

17-
export function mockPreferredColorScheme(theme, options = {}) {
18+
export function mockPreferredColorScheme(color, options = {}) {
1819
Object.defineProperty(window, 'matchMedia', {
1920
writable: true,
20-
value: jest.fn().mockImplementation((query) => ({
21-
matches: theme === 'dark',
21+
value: jest.fn().mockImplementation(query => ({
22+
matches: color === 'dark',
2223
media: query,
2324
onchange: null,
2425
addEventListener: jest.fn(),

0 commit comments

Comments
 (0)