-
-
Notifications
You must be signed in to change notification settings - Fork 1.6k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
* feat(docs): update dark mode content * feat(hooks): @nextui-org/use-theme * chore(docs): revise ThemeSwitcher code * refactor(hooks): simplify useTheme and support custom theme names * feat(hooks): add use-theme test cases * feat(changeset): add changeset * refactor(hooks): make localStorageMock globally and clear before each test * fix(docs): typo * fix(hooks): coderabbitai comments * chore(hooks): remove unnecessary + * chore(changeset): change to minor * feat(hooks): handle system theme * chore(hooks): add EOL * refactor(hooks): add default theme * refactor(hooks): revise useTheme * refactor(hooks): resolve pr comments * refactor(hooks): resolve pr comments * refactor(hooks): resolve pr comments * refactor(hooks): remove unused theme in dependency array * chore(docs): typos * refactor(hooks): mark system as key for system theme * chore: merged with canary --------- Co-authored-by: Junior Garcia <[email protected]>
- Loading branch information
1 parent
3f0d81b
commit ad7e261
Showing
9 changed files
with
379 additions
and
26 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
--- | ||
"@nextui-org/use-theme": minor | ||
--- | ||
|
||
introduce `use-theme` hook |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,55 @@ | ||
# @nextui-org/use-theme | ||
|
||
React hook to switch between light and dark themes | ||
|
||
## Installation | ||
|
||
```sh | ||
yarn add @nextui-org/use-theme | ||
# or | ||
npm i @nextui-org/use-theme | ||
``` | ||
|
||
## Usage | ||
|
||
Import `useTheme` | ||
|
||
```tsx | ||
import {useTheme} from "@nextui-org/use-theme"; | ||
``` | ||
|
||
### theme | ||
|
||
```tsx | ||
// `theme` is the active theme name | ||
// by default, it will use the one in localStorage. | ||
// if it is no such value in localStorage, `light` theme will be used | ||
const {theme} = useTheme(); | ||
``` | ||
|
||
### setTheme | ||
|
||
You can use any theme name you want, but make sure it exists in your | ||
`tailwind.config.js` file. See [Create Theme](https://nextui.org/docs/customization/create-theme) for more details. | ||
|
||
```tsx | ||
// set `theme` by using `setTheme` | ||
const {setTheme} = useTheme(); | ||
// setting to light theme | ||
setTheme('light') | ||
// setting to dark theme | ||
setTheme('dark') | ||
// setting to purple-dark theme | ||
setTheme('purple-dark') | ||
``` | ||
|
||
## Contribution | ||
|
||
Yes please! See the | ||
[contributing guidelines](https://github.com/nextui-org/nextui/blob/master/CONTRIBUTING.md) | ||
for details. | ||
|
||
## License | ||
|
||
This project is licensed under the terms of the | ||
[MIT license](https://github.com/nextui-org/nextui/blob/master/LICENSE). |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,147 @@ | ||
import * as React from "react"; | ||
import {render, act} from "@testing-library/react"; | ||
|
||
import {useTheme, ThemeProps, Theme} from "../src"; | ||
|
||
const TestComponent = ({defaultTheme}: {defaultTheme?: Theme}) => { | ||
const {theme, setTheme} = useTheme(defaultTheme); | ||
|
||
return ( | ||
<div> | ||
<span data-testid="theme-display">{theme}</span> | ||
<button type="button" onClick={() => setTheme(ThemeProps.DARK)}> | ||
Set Dark | ||
</button> | ||
<button type="button" onClick={() => setTheme(ThemeProps.LIGHT)}> | ||
Set Light | ||
</button> | ||
<button type="button" onClick={() => setTheme(ThemeProps.SYSTEM)}> | ||
Set System | ||
</button> | ||
</div> | ||
); | ||
}; | ||
|
||
TestComponent.displayName = "TestComponent"; | ||
|
||
const localStorageMock = (() => { | ||
let store: {[key: string]: string} = {}; | ||
|
||
return { | ||
getItem: (key: string) => store[key] || null, | ||
setItem: (key: string, value: string) => { | ||
store[key] = value; | ||
}, | ||
clear: () => { | ||
store = {}; | ||
}, | ||
}; | ||
})(); | ||
|
||
Object.defineProperty(window, "localStorage", { | ||
value: localStorageMock, | ||
}); | ||
|
||
describe("useTheme hook", () => { | ||
beforeEach(() => { | ||
jest.clearAllMocks(); | ||
|
||
localStorage.clear(); | ||
|
||
document.documentElement.className = ""; | ||
}); | ||
|
||
it("should initialize with default theme if no theme is stored in localStorage", () => { | ||
const {getByTestId} = render(<TestComponent />); | ||
|
||
expect(getByTestId("theme-display").textContent).toBe(ThemeProps.LIGHT); | ||
expect(document.documentElement.classList.contains(ThemeProps.LIGHT)).toBe(true); | ||
}); | ||
|
||
it("should initialize with the given theme if no theme is stored in localStorage", () => { | ||
const customTheme = "purple-dark"; | ||
const {getByTestId} = render(<TestComponent defaultTheme={customTheme} />); | ||
|
||
expect(getByTestId("theme-display").textContent).toBe(customTheme); | ||
expect(document.documentElement.classList.contains(customTheme)).toBe(true); | ||
}); | ||
|
||
it("should initialize with stored theme from localStorage", () => { | ||
localStorage.setItem(ThemeProps.KEY, ThemeProps.DARK); | ||
|
||
const {getByTestId} = render(<TestComponent />); | ||
|
||
expect(localStorage.getItem(ThemeProps.KEY)).toBe(ThemeProps.DARK); | ||
|
||
expect(getByTestId("theme-display").textContent).toBe(ThemeProps.DARK); | ||
expect(document.documentElement.classList.contains(ThemeProps.DARK)).toBe(true); | ||
}); | ||
|
||
it("should set new theme correctly and update localStorage and DOM (dark)", () => { | ||
const {getByText, getByTestId} = render(<TestComponent />); | ||
|
||
act(() => { | ||
getByText("Set Dark").click(); | ||
}); | ||
expect(getByTestId("theme-display").textContent).toBe(ThemeProps.DARK); | ||
expect(localStorage.getItem(ThemeProps.KEY)).toBe(ThemeProps.DARK); | ||
expect(document.documentElement.classList.contains(ThemeProps.DARK)).toBe(true); | ||
}); | ||
|
||
it("should set new theme correctly and update localStorage and DOM (light)", () => { | ||
const {getByText, getByTestId} = render(<TestComponent />); | ||
|
||
act(() => { | ||
getByText("Set Light").click(); | ||
}); | ||
expect(getByTestId("theme-display").textContent).toBe(ThemeProps.LIGHT); | ||
expect(localStorage.getItem(ThemeProps.KEY)).toBe(ThemeProps.LIGHT); | ||
expect(document.documentElement.classList.contains(ThemeProps.LIGHT)).toBe(true); | ||
}); | ||
|
||
it("should set new theme correctly and update localStorage and DOM (system - prefers-color-scheme: light)", () => { | ||
const {getByText, getByTestId} = render(<TestComponent />); | ||
|
||
Object.defineProperty(window, "matchMedia", { | ||
writable: true, | ||
value: jest.fn().mockImplementation((query) => ({ | ||
matches: false, | ||
media: query, | ||
onchange: null, | ||
addEventListener: jest.fn(), | ||
removeEventListener: jest.fn(), | ||
dispatchEvent: jest.fn(), | ||
})), | ||
}); | ||
|
||
act(() => { | ||
getByText("Set System").click(); | ||
}); | ||
expect(getByTestId("theme-display").textContent).toBe(ThemeProps.SYSTEM); | ||
expect(localStorage.getItem(ThemeProps.KEY)).toBe(ThemeProps.SYSTEM); | ||
expect(document.documentElement.classList.contains(ThemeProps.LIGHT)).toBe(true); | ||
}); | ||
|
||
it("should set new theme correctly and update localStorage and DOM (system - prefers-color-scheme: dark)", () => { | ||
const {getByText, getByTestId} = render(<TestComponent />); | ||
|
||
Object.defineProperty(window, "matchMedia", { | ||
writable: true, | ||
value: jest.fn().mockImplementation((query) => ({ | ||
matches: true, | ||
media: query, | ||
onchange: null, | ||
addEventListener: jest.fn(), | ||
removeEventListener: jest.fn(), | ||
dispatchEvent: jest.fn(), | ||
})), | ||
}); | ||
|
||
act(() => { | ||
getByText("Set System").click(); | ||
}); | ||
expect(getByTestId("theme-display").textContent).toBe(ThemeProps.SYSTEM); | ||
expect(localStorage.getItem(ThemeProps.KEY)).toBe(ThemeProps.SYSTEM); | ||
expect(document.documentElement.classList.contains(ThemeProps.DARK)).toBe(true); | ||
}); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,52 @@ | ||
{ | ||
"name": "@nextui-org/use-theme", | ||
"version": "2.0.0", | ||
"description": "React hook to switch between light and dark themes", | ||
"keywords": [ | ||
"use-theme" | ||
], | ||
"author": "WK Wong <[email protected]>", | ||
"homepage": "https://nextui.org", | ||
"license": "MIT", | ||
"main": "src/index.ts", | ||
"sideEffects": false, | ||
"files": [ | ||
"dist" | ||
], | ||
"publishConfig": { | ||
"access": "public" | ||
}, | ||
"repository": { | ||
"type": "git", | ||
"url": "git+https://github.com/nextui-org/nextui.git", | ||
"directory": "packages/hooks/use-theme" | ||
}, | ||
"bugs": { | ||
"url": "https://github.com/nextui-org/nextui/issues" | ||
}, | ||
"scripts": { | ||
"build": "tsup src --dts", | ||
"build:fast": "tsup src", | ||
"dev": "pnpm build:fast --watch", | ||
"clean": "rimraf dist .turbo", | ||
"typecheck": "tsc --noEmit", | ||
"prepack": "clean-package", | ||
"postpack": "clean-package restore" | ||
}, | ||
"peerDependencies": { | ||
"react": ">=18" | ||
}, | ||
"devDependencies": { | ||
"clean-package": "2.2.0", | ||
"react": "^18.0.0" | ||
}, | ||
"clean-package": "../../../clean-package.config.json", | ||
"tsup": { | ||
"clean": true, | ||
"target": "es2019", | ||
"format": [ | ||
"cjs", | ||
"esm" | ||
] | ||
} | ||
} |
Oops, something went wrong.