Bluesky app fork with some witchin' additions 馃挮
witchsky.app
bluesky
fork
client
1import {createContext, useCallback, useContext, useMemo, useState} from 'react'
2import {createTheme, type Theme, type ThemeName} from '@bsky.app/alf'
3import chroma from 'chroma-js'
4
5import {useThemePrefs} from '#/state/shell/color-mode'
6import {
7 computeFontScaleMultiplier,
8 getFontFamily,
9 getFontScale,
10 setFontFamily as persistFontFamily,
11 setFontScale as persistFontScale,
12} from '#/alf/fonts'
13import {
14 blackskyscheme,
15 blueskyscheme,
16 catppuccinscheme,
17 cyanscheme,
18 deerscheme,
19 evergardenscheme,
20 kittyscheme,
21 type Palette,
22 reddwarfscheme,
23 themes,
24 witchskyscheme,
25 zeppelinscheme,
26} from '#/alf/themes'
27import {type Device} from '#/storage'
28import {getMaterial3Colors} from './util/material3Theme'
29import {
30 MaterialYouPaletteProvider,
31 useMaterialYouPalette,
32} from './util/materialYou'
33
34export {
35 type TextStyleProp,
36 type Theme,
37 utils,
38 type ViewStyleProp,
39} from '@bsky.app/alf'
40export {atoms} from '#/alf/atoms'
41export * from '#/alf/breakpoints'
42export * from '#/alf/fonts'
43export * as tokens from '#/alf/tokens'
44export * from '#/alf/util/flatten'
45export * from '#/alf/util/platform'
46export * from '#/alf/util/themeSelector'
47export * from '#/alf/util/useGutters'
48
49export type Alf = {
50 themeName: ThemeName
51 theme: Theme
52 themes: typeof themes
53 fonts: {
54 scale: Exclude<Device['fontScale'], undefined>
55 scaleMultiplier: number
56 family: Device['fontFamily']
57 setFontScale: (fontScale: Exclude<Device['fontScale'], undefined>) => void
58 setFontFamily: (fontFamily: Device['fontFamily']) => void
59 }
60 /**
61 * Feature flags or other gated options
62 */
63 flags: {}
64}
65
66/*
67 * Context
68 */
69export const Context = createContext<Alf>({
70 themeName: 'light',
71 theme: themes.light,
72 themes,
73 fonts: {
74 scale: getFontScale(),
75 scaleMultiplier: computeFontScaleMultiplier(getFontScale()),
76 family: getFontFamily(),
77 setFontScale: () => {},
78 setFontFamily: () => {},
79 },
80 flags: {},
81})
82Context.displayName = 'AlfContext'
83
84export type SchemeType = typeof themes
85
86export function changeHue(colorStr: string, hueShift: number) {
87 if (!hueShift || hueShift === 0) return colorStr
88
89 const color = chroma(colorStr).oklch()
90
91 const newHue = (color[2] + hueShift + 360) % 360
92
93 return chroma.oklch(color[0], color[1], newHue).hex()
94}
95
96export function shiftPalette(palette: Palette, hueShift: number): Palette {
97 const newPalette = {...palette}
98 const keys = Object.keys(newPalette) as Array<keyof Palette>
99
100 keys.forEach(key => {
101 if (
102 key.startsWith('positive_') ||
103 key.startsWith('negative_') ||
104 key === 'like' ||
105 key === 'pink' ||
106 key === 'yellow'
107 ) {
108 return
109 }
110 newPalette[key] = changeHue(newPalette[key], hueShift)
111 })
112
113 return newPalette
114}
115
116export function hueShifter(scheme: SchemeType, hueShift: number): SchemeType {
117 if (!hueShift || hueShift === 0) {
118 return scheme
119 }
120
121 const lightPalette = shiftPalette(scheme.lightPalette, hueShift)
122 const darkPalette = shiftPalette(scheme.darkPalette, hueShift)
123 const dimPalette = shiftPalette(scheme.dimPalette, hueShift)
124
125 const light = createTheme({
126 scheme: 'light',
127 name: 'light',
128 palette: lightPalette,
129 })
130
131 const dark = createTheme({
132 scheme: 'dark',
133 name: 'dark',
134 palette: darkPalette,
135 options: {
136 shadowOpacity: 0.4,
137 },
138 })
139
140 const dim = createTheme({
141 scheme: 'dark',
142 name: 'dim',
143 palette: dimPalette,
144 options: {
145 shadowOpacity: 0.4,
146 },
147 })
148
149 return {
150 lightPalette,
151 darkPalette,
152 dimPalette,
153 light,
154 dark,
155 dim,
156 }
157}
158
159export function useScheme(): SchemeType {
160 const {hue, colorScheme} = useThemePrefs()
161 const palette = useMaterialYouPalette()
162
163 return useMemo(() => {
164 let currentScheme = themes
165 switch (colorScheme) {
166 case 'witchsky':
167 currentScheme = witchskyscheme
168 break
169 case 'bluesky':
170 currentScheme = blueskyscheme
171 break
172 case 'blacksky':
173 currentScheme = blackskyscheme
174 break
175 case 'deer':
176 currentScheme = deerscheme
177 break
178 case 'zeppelin':
179 currentScheme = zeppelinscheme
180 break
181 case 'kitty':
182 currentScheme = kittyscheme
183 break
184 case 'reddwarf':
185 currentScheme = reddwarfscheme
186 break
187 case 'catppuccin':
188 currentScheme = catppuccinscheme
189 break
190 case 'evergarden':
191 currentScheme = evergardenscheme
192 break
193 case 'cyan base':
194 currentScheme = cyanscheme
195 break
196 case 'material3':
197 currentScheme = getMaterial3Colors(palette).scheme
198 break
199 default:
200 currentScheme = themes
201 break
202 }
203
204 return hueShifter(currentScheme, hue)
205 }, [colorScheme, hue, palette])
206}
207
208function ThemeProviderInner({
209 children,
210 theme: themeName,
211}: React.PropsWithChildren<{theme: ThemeName}>) {
212 const currentScheme = useScheme()
213 const [fontScale, setFontScale] = useState<Alf['fonts']['scale']>(() =>
214 getFontScale(),
215 )
216 const [fontScaleMultiplier, setFontScaleMultiplier] = useState(() =>
217 computeFontScaleMultiplier(fontScale),
218 )
219 const setFontScaleAndPersist = useCallback<Alf['fonts']['setFontScale']>(
220 fs => {
221 setFontScale(fs)
222 persistFontScale(fs)
223 setFontScaleMultiplier(computeFontScaleMultiplier(fs))
224 },
225 [setFontScale],
226 )
227 const [fontFamily, setFontFamily] = useState<Alf['fonts']['family']>(() =>
228 getFontFamily(),
229 )
230 const setFontFamilyAndPersist = useCallback<Alf['fonts']['setFontFamily']>(
231 ff => {
232 setFontFamily(ff)
233 persistFontFamily(ff)
234 },
235 [setFontFamily],
236 )
237
238 const value = useMemo<Alf>(() => {
239 return {
240 themes: currentScheme,
241 themeName: themeName,
242 theme: currentScheme[themeName],
243 fonts: {
244 scale: fontScale,
245 scaleMultiplier: fontScaleMultiplier,
246 family: fontFamily,
247 setFontScale: setFontScaleAndPersist,
248 setFontFamily: setFontFamilyAndPersist,
249 },
250 flags: {},
251 }
252 }, [
253 currentScheme,
254 themeName,
255 fontScale,
256 fontScaleMultiplier,
257 fontFamily,
258 setFontScaleAndPersist,
259 setFontFamilyAndPersist,
260 ])
261
262 return <Context.Provider value={value}>{children}</Context.Provider>
263}
264
265export function ThemeProvider({
266 children,
267 theme: themeName,
268}: React.PropsWithChildren<{theme: ThemeName}>) {
269 const {material3Accent, material3Style} = useThemePrefs()
270 return (
271 <MaterialYouPaletteProvider accent={material3Accent} style={material3Style}>
272 <ThemeProviderInner theme={themeName}>{children}</ThemeProviderInner>
273 </MaterialYouPaletteProvider>
274 )
275}
276
277export function useAlf() {
278 return useContext(Context)
279}
280
281export function useTheme(theme?: ThemeName) {
282 const alf = useAlf()
283 return useMemo(() => {
284 return theme ? alf.themes[theme] : alf.theme
285 }, [theme, alf])
286}