forked from
jollywhoppers.com/witchsky.app
Bluesky app fork with some witchin' additions 馃挮
1import React 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 deerscheme,
18 kittyscheme,
19 type Palette,
20 reddwarfscheme,
21 themes,
22 witchskyscheme,
23 zeppelinscheme,
24} from '#/alf/themes'
25import {type Device} from '#/storage'
26
27export {
28 type TextStyleProp,
29 type Theme,
30 utils,
31 type ViewStyleProp,
32} from '@bsky.app/alf'
33export {atoms} from '#/alf/atoms'
34export * from '#/alf/breakpoints'
35export * from '#/alf/fonts'
36export * as tokens from '#/alf/tokens'
37export * from '#/alf/util/flatten'
38export * from '#/alf/util/platform'
39export * from '#/alf/util/themeSelector'
40export * from '#/alf/util/useGutters'
41
42export type Alf = {
43 themeName: ThemeName
44 theme: Theme
45 themes: typeof themes
46 fonts: {
47 scale: Exclude<Device['fontScale'], undefined>
48 scaleMultiplier: number
49 family: Device['fontFamily']
50 setFontScale: (fontScale: Exclude<Device['fontScale'], undefined>) => void
51 setFontFamily: (fontFamily: Device['fontFamily']) => void
52 }
53 /**
54 * Feature flags or other gated options
55 */
56 flags: {}
57}
58
59/*
60 * Context
61 */
62export const Context = React.createContext<Alf>({
63 themeName: 'light',
64 theme: themes.light,
65 themes,
66 fonts: {
67 scale: getFontScale(),
68 scaleMultiplier: computeFontScaleMultiplier(getFontScale()),
69 family: getFontFamily(),
70 setFontScale: () => {},
71 setFontFamily: () => {},
72 },
73 flags: {},
74})
75Context.displayName = 'AlfContext'
76
77export type SchemeType = typeof themes
78
79export function changeHue(colorStr: string, hueShift: number) {
80 if (!hueShift || hueShift === 0) return colorStr
81
82 const color = chroma(colorStr).oklch()
83
84 const newHue = (color[2] + hueShift + 360) % 360
85
86 return chroma.oklch(color[0], color[1], newHue).hex()
87}
88
89export function shiftPalette(palette: Palette, hueShift: number): Palette {
90 const newPalette = {...palette}
91 const keys = Object.keys(newPalette) as Array<keyof Palette>
92
93 keys.forEach(key => {
94 newPalette[key] = changeHue(newPalette[key], hueShift)
95 })
96
97 return newPalette
98}
99
100export function hueShifter(scheme: SchemeType, hueShift: number): SchemeType {
101 if (!hueShift || hueShift === 0) {
102 return scheme
103 }
104
105 const lightPalette = shiftPalette(scheme.lightPalette, hueShift)
106 const darkPalette = shiftPalette(scheme.darkPalette, hueShift)
107 const dimPalette = shiftPalette(scheme.dimPalette, hueShift)
108
109 const light = createTheme({
110 scheme: 'light',
111 name: 'light',
112 palette: lightPalette,
113 })
114
115 const dark = createTheme({
116 scheme: 'dark',
117 name: 'dark',
118 palette: darkPalette,
119 options: {
120 shadowOpacity: 0.4,
121 },
122 })
123
124 const dim = createTheme({
125 scheme: 'dark',
126 name: 'dim',
127 palette: dimPalette,
128 options: {
129 shadowOpacity: 0.4,
130 },
131 })
132
133 return {
134 lightPalette,
135 darkPalette,
136 dimPalette,
137 light,
138 dark,
139 dim,
140 }
141}
142
143export function selectScheme(colorScheme: string | undefined): SchemeType {
144 switch (colorScheme) {
145 case 'witchsky':
146 return witchskyscheme
147 case 'bluesky':
148 return blueskyscheme
149 case 'blacksky':
150 return blackskyscheme
151 case 'deer':
152 return deerscheme
153 case 'zeppelin':
154 return zeppelinscheme
155 case 'kitty':
156 return kittyscheme
157 case 'reddwarf':
158 return reddwarfscheme
159 case 'catppuccin':
160 return catppuccinscheme
161 default:
162 return themes
163 }
164}
165
166export function ThemeProvider({
167 children,
168 theme: themeName,
169}: React.PropsWithChildren<{theme: ThemeName}>) {
170 const {colorScheme, hue} = useThemePrefs()
171 const currentScheme = selectScheme(colorScheme)
172 const [fontScale, setFontScale] = React.useState<Alf['fonts']['scale']>(() =>
173 getFontScale(),
174 )
175 const [fontScaleMultiplier, setFontScaleMultiplier] = React.useState(() =>
176 computeFontScaleMultiplier(fontScale),
177 )
178 const setFontScaleAndPersist = React.useCallback<
179 Alf['fonts']['setFontScale']
180 >(
181 fs => {
182 setFontScale(fs)
183 persistFontScale(fs)
184 setFontScaleMultiplier(computeFontScaleMultiplier(fs))
185 },
186 [setFontScale],
187 )
188 const [fontFamily, setFontFamily] = React.useState<Alf['fonts']['family']>(
189 () => getFontFamily(),
190 )
191 const setFontFamilyAndPersist = React.useCallback<
192 Alf['fonts']['setFontFamily']
193 >(
194 ff => {
195 setFontFamily(ff)
196 persistFontFamily(ff)
197 },
198 [setFontFamily],
199 )
200
201 const value = React.useMemo<Alf>(
202 () => ({
203 themes: hueShifter(currentScheme, hue),
204 themeName: themeName,
205 theme: hueShifter(currentScheme, hue)[themeName],
206 fonts: {
207 scale: fontScale,
208 scaleMultiplier: fontScaleMultiplier,
209 family: fontFamily,
210 setFontScale: setFontScaleAndPersist,
211 setFontFamily: setFontFamilyAndPersist,
212 },
213 flags: {},
214 }),
215 [
216 currentScheme,
217 hue,
218 themeName,
219 fontScale,
220 fontScaleMultiplier,
221 fontFamily,
222 setFontScaleAndPersist,
223 setFontFamilyAndPersist,
224 ],
225 )
226
227 return <Context.Provider value={value}>{children}</Context.Provider>
228}
229
230export function useAlf() {
231 return React.useContext(Context)
232}
233
234export function useTheme(theme?: ThemeName) {
235 const alf = useAlf()
236 return React.useMemo(() => {
237 return theme ? alf.themes[theme] : alf.theme
238 }, [theme, alf])
239}