Bluesky app fork with some witchin' additions 馃挮
at main 239 lines 5.6 kB view raw
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}