Skip to content

Commit 499c55c

Browse files
committed
chore: add demo and README
1 parent c9447b5 commit 499c55c

11 files changed

+4278
-3
lines changed

README.md

+233-3
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,233 @@
1-
interface Window {
2-
palettez: typeof import('palettez')
3-
}
1+
# Palettez
2+
3+
A flexible and powerful theme management library for JavaScript applications.
4+
5+
## Features
6+
7+
- Manage parallel themes with multiple options, for eg:
8+
- Color scheme: light, dark, system
9+
- Contrast preference: standard, high
10+
- Spacing: compact, comfortable, spacious
11+
- Persist theme selection to client or server storage
12+
- Dynamically change themes based on system settings
13+
- Sync theme selection across tabs and windows
14+
- No theme flicker on page load
15+
16+
## Demo
17+
18+
- Client-side persistence with localStorage and server-side persistence with cookies
19+
20+
[![Open in StackBlitz](https://developer.stackblitz.com/img/open_in_stackblitz.svg)](https://stackblitz.com/fork/github/universse/palettez/tree/main/demo?title=Palettez%20Demo&file=src%2Fpages%2Findex.astro,src%2Fpages%2Fssr.astro)
21+
22+
## Installation
23+
24+
To install:
25+
26+
```bash
27+
npm i palettez
28+
# or
29+
yarn add palettez
30+
# or
31+
pnpm add palettez
32+
```
33+
34+
## Basic Usage
35+
36+
For client-side persistence (eg. localStorage), it's recommended to initialize Palettez in a synchronous script to avoid theme flicker on page load.If your project's bundler supports importing static asset as string, you can inline the minified version of Palettez to reduce the number of HTTP requests. Check out the demo for example usage with Astro and Vite.
37+
38+
```html
39+
<script src="https://unpkg.com/palettez"></script>
40+
<!-- or -->
41+
<script src="https://cdn.jsdelivr.net/npm/palettez"></script>
42+
43+
<script>
44+
;(() => {
45+
const themeManager = window.palettez.create({
46+
config: {
47+
colorScheme: {
48+
label: 'Color scheme',
49+
options: {
50+
system: {
51+
value: 'System',
52+
isDefault: true,
53+
media: {
54+
query: '(prefers-color-scheme: dark)',
55+
ifMatch: 'dark',
56+
ifNotMatch: 'light',
57+
},
58+
},
59+
light: { value: 'Light' },
60+
dark: { value: 'Dark' },
61+
},
62+
},
63+
},
64+
})
65+
66+
themeManager.subscribe((_, resolvedThemes) => {
67+
Object.entries(resolvedThemes).forEach(([theme, optionKey]) => {
68+
document.documentElement.dataset[theme] = optionKey
69+
})
70+
})
71+
72+
themeManager.restore()
73+
themeManager.sync()
74+
})()
75+
</script>
76+
```
77+
78+
If you are using TypeScript, add `palettez/global` to `compilerOptions.types` in `tsconfig.json`.
79+
80+
```json
81+
{
82+
"compilerOptions": {
83+
"types": ["palettez/global"]
84+
}
85+
}
86+
```
87+
88+
## API
89+
90+
### `create`
91+
92+
```ts
93+
import { create } from 'palettez'
94+
95+
const themeManager = create({
96+
// optional, default: 'palettez'
97+
key: 'palettez',
98+
99+
// required, specify theme and options
100+
config: {
101+
colorScheme: {
102+
label: 'Color scheme',
103+
options: {
104+
system: {
105+
value: 'System',
106+
isDefault: true,
107+
media: {
108+
query: '(prefers-color-scheme: dark)',
109+
ifMatch: 'dark',
110+
ifNotMatch: 'light',
111+
},
112+
},
113+
light: { value: 'Light' },
114+
dark: { value: 'Dark' },
115+
},
116+
},
117+
118+
contrast: {
119+
label: 'Contrast',
120+
options: {
121+
system: {
122+
value: 'System',
123+
isDefault: true,
124+
media: {
125+
query: '(prefers-contrast: more) and (forced-colors: none)',
126+
ifMatch: 'more',
127+
ifNotMatch: 'standard',
128+
},
129+
},
130+
standard: { value: 'Standard' },
131+
high: { value: 'High' },
132+
},
133+
},
134+
},
135+
136+
// optional, specify your own storage solution
137+
getStorage: () => {
138+
return {
139+
getItem: (key: string) => {
140+
return JSON.parse(window.localStorage.getItem(key) || 'null')
141+
},
142+
143+
setItem: (key: string, value: object) => {
144+
window.localStorage.setItem(key, JSON.stringify(value))
145+
},
146+
147+
removeItem: (key: string) => {
148+
window.localStorage.removeItem(key)
149+
},
150+
151+
// optional, useful for syncing theme selection across tabs and windows
152+
watch: (cb) => {
153+
const controller = new AbortController()
154+
155+
window.addEventListener(
156+
'storage',
157+
(e) => {
158+
const persistedThemes = JSON.parse(e.newValue || 'null')
159+
cb(e.key, persistedThemes)
160+
},
161+
{ signal: controller.signal },
162+
)
163+
164+
return () => {
165+
controller.abort()
166+
}
167+
},
168+
}
169+
}
170+
})
171+
```
172+
173+
### `read`
174+
175+
```ts
176+
import { read } from 'palettez'
177+
178+
const themeManager = read('palettez')
179+
```
180+
181+
### Methods
182+
183+
```ts
184+
themeManager.getThemes() // { colorScheme: 'system', contrast: 'standard' }
185+
themeManager.getResolvedThemes() // { colorScheme: 'light', contrast: 'standard' }
186+
themeManager.setThemes({ contrast: 'high' })
187+
themeManager.restore() // restore persisted theme selection
188+
themeManager.sync() // useful for syncing theme selection across tabs and windows
189+
themeManager.clear() // clear persisted theme selection
190+
themeManager.subscribe((themes, resolvedThemes) => { /* ... */ })
191+
```
192+
193+
## React Integration
194+
195+
Ensure that you have called `create` before `usePalettez`.
196+
197+
```tsx
198+
import { usePalettez } from 'palettez/react'
199+
200+
export function ThemeSelect() {
201+
const {
202+
themesAndOptions,
203+
themes,
204+
setThemes,
205+
206+
getResolvedThemes,
207+
restore,
208+
sync,
209+
clear,
210+
subscribe,
211+
} = usePalettez('palettez')
212+
213+
return themesAndOptions.map((theme) => (
214+
<div key={theme.key}>
215+
<label htmlFor={theme.key}>{theme.label}</label>
216+
<select
217+
id={theme.key}
218+
name={theme.key}
219+
onChange={(e) => {
220+
setThemes({ [theme.key]: e.target.value })
221+
}}
222+
value={themes[theme.key]}
223+
>
224+
{theme.options.map((option) => (
225+
<option key={option.key} value={option.key}>
226+
{option.value}
227+
</option>
228+
))}
229+
</select>
230+
</div>
231+
))
232+
}
233+
```

demo/.stackblitzrc

+7
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
{
2+
"installDependencies": true,
3+
"startCommand": "npm run dev",
4+
"env": {
5+
"ENABLE_CJS_IMPORTS": true
6+
}
7+
}

demo/astro.config.js

+11
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
import node from '@astrojs/node'
2+
import react from '@astrojs/react'
3+
import { defineConfig } from 'astro/config'
4+
5+
export default defineConfig({
6+
adapter: node({
7+
mode: 'standalone',
8+
}),
9+
integrations: [react()],
10+
output: 'hybrid',
11+
})

demo/package.json

+20
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
{
2+
"name": "demo",
3+
"private": true,
4+
"type": "module",
5+
"scripts": {
6+
"dev": "astro dev --port 3000"
7+
},
8+
"dependencies": {
9+
"astro": "4.13.2",
10+
"palettez": "*",
11+
"react": "18.3.1",
12+
"react-dom": "18.3.1"
13+
},
14+
"devDependencies": {
15+
"@astrojs/node": "8.3.2",
16+
"@astrojs/react": "3.6.1",
17+
"@types/react": "18.3.3",
18+
"@types/react-dom": "18.3.0"
19+
}
20+
}

0 commit comments

Comments
 (0)