Bluesky's "Application Layout Framework"
at main 252 lines 5.2 kB view raw
1import {atoms} from './atoms' 2import {alpha} from './utils/alpha' 3 4import { 5 Palette, 6 DEFAULT_PALETTE, 7 DEFAULT_SUBDUED_PALETTE, 8 invertPalette, 9} from './palette' 10import {type ShadowStyle} from './atoms/types' 11 12export const themes = createThemes({ 13 defaultPalette: DEFAULT_PALETTE, 14 subduedPalette: DEFAULT_SUBDUED_PALETTE, 15}) 16 17export type ThemeAtoms = { 18 text: { 19 color: string 20 } 21 text_contrast_low: { 22 color: string 23 } 24 text_contrast_medium: { 25 color: string 26 } 27 text_contrast_high: { 28 color: string 29 } 30 text_inverted: { 31 color: string 32 } 33 bg: { 34 backgroundColor: string 35 } 36 bg_contrast_25: { 37 backgroundColor: string 38 } 39 bg_contrast_50: { 40 backgroundColor: string 41 } 42 bg_contrast_100: { 43 backgroundColor: string 44 } 45 bg_contrast_200: { 46 backgroundColor: string 47 } 48 bg_contrast_300: { 49 backgroundColor: string 50 } 51 bg_contrast_400: { 52 backgroundColor: string 53 } 54 bg_contrast_500: { 55 backgroundColor: string 56 } 57 bg_contrast_600: { 58 backgroundColor: string 59 } 60 bg_contrast_700: { 61 backgroundColor: string 62 } 63 bg_contrast_800: { 64 backgroundColor: string 65 } 66 bg_contrast_900: { 67 backgroundColor: string 68 } 69 bg_contrast_950: { 70 backgroundColor: string 71 } 72 bg_contrast_975: { 73 backgroundColor: string 74 } 75 border_contrast_low: { 76 borderColor: string 77 } 78 border_contrast_medium: { 79 borderColor: string 80 } 81 border_contrast_high: { 82 borderColor: string 83 } 84 shadow_sm: ShadowStyle 85 shadow_md: ShadowStyle 86 shadow_lg: ShadowStyle 87} 88 89/** 90 * Categorical representation of the theme 91 */ 92export type ThemeScheme = 'light' | 'dark' 93 94/** 95 * Specific theme name, including low-contrast variants 96 */ 97export type ThemeName = 'light' | 'dark' | 'dim' 98 99/** 100 * A theme object, containing the color palette and atoms for the theme 101 */ 102export type Theme = { 103 scheme: ThemeScheme 104 name: ThemeName 105 palette: Palette 106 atoms: ThemeAtoms 107} 108 109export function createTheme({ 110 scheme, 111 name, 112 palette, 113 options = {}, 114}: { 115 scheme: ThemeScheme 116 name: ThemeName 117 palette: Palette 118 options?: { 119 shadowOpacity?: number 120 } 121}): Theme { 122 const shadowOpacity = options.shadowOpacity ?? 0.1 123 const shadowColor = alpha(palette.black, shadowOpacity) 124 return { 125 scheme, 126 name, 127 palette, 128 atoms: { 129 text: { 130 color: palette.contrast_1000, 131 }, 132 text_contrast_low: { 133 color: palette.contrast_400, 134 }, 135 text_contrast_medium: { 136 color: palette.contrast_700, 137 }, 138 text_contrast_high: { 139 color: palette.contrast_900, 140 }, 141 text_inverted: { 142 color: palette.contrast_0, 143 }, 144 bg: { 145 backgroundColor: palette.contrast_0, 146 }, 147 bg_contrast_25: { 148 backgroundColor: palette.contrast_25, 149 }, 150 bg_contrast_50: { 151 backgroundColor: palette.contrast_50, 152 }, 153 bg_contrast_100: { 154 backgroundColor: palette.contrast_100, 155 }, 156 bg_contrast_200: { 157 backgroundColor: palette.contrast_200, 158 }, 159 bg_contrast_300: { 160 backgroundColor: palette.contrast_300, 161 }, 162 bg_contrast_400: { 163 backgroundColor: palette.contrast_400, 164 }, 165 bg_contrast_500: { 166 backgroundColor: palette.contrast_500, 167 }, 168 bg_contrast_600: { 169 backgroundColor: palette.contrast_600, 170 }, 171 bg_contrast_700: { 172 backgroundColor: palette.contrast_700, 173 }, 174 bg_contrast_800: { 175 backgroundColor: palette.contrast_800, 176 }, 177 bg_contrast_900: { 178 backgroundColor: palette.contrast_900, 179 }, 180 bg_contrast_950: { 181 backgroundColor: palette.contrast_950, 182 }, 183 bg_contrast_975: { 184 backgroundColor: palette.contrast_975, 185 }, 186 border_contrast_low: { 187 borderColor: palette.contrast_100, 188 }, 189 border_contrast_medium: { 190 borderColor: palette.contrast_200, 191 }, 192 border_contrast_high: { 193 borderColor: palette.contrast_300, 194 }, 195 shadow_sm: { 196 ...atoms.shadow_sm, 197 shadowColor: palette.black, 198 boxShadow: `0 4px 6px -1px ${shadowColor}, 0 2px 4px -2px ${shadowColor}`, 199 }, 200 shadow_md: { 201 ...atoms.shadow_md, 202 shadowColor: palette.black, 203 boxShadow: `0 10px 15px -3px ${shadowColor}, 0 4px 6px -4px ${shadowColor}`, 204 }, 205 shadow_lg: { 206 ...atoms.shadow_lg, 207 shadowColor: palette.black, 208 boxShadow: `0 20px 25px -5px ${shadowColor}, 0 8px 10px -6px ${shadowColor}`, 209 }, 210 }, 211 } 212} 213 214export function createThemes({ 215 defaultPalette, 216 subduedPalette, 217}: { 218 defaultPalette: Palette 219 subduedPalette: Palette 220}): { 221 light: Theme 222 dark: Theme 223 dim: Theme 224} { 225 const light = createTheme({ 226 scheme: 'light', 227 name: 'light', 228 palette: defaultPalette, 229 }) 230 const dark = createTheme({ 231 scheme: 'dark', 232 name: 'dark', 233 palette: invertPalette(defaultPalette), 234 options: { 235 shadowOpacity: 0.4, 236 }, 237 }) 238 const dim = createTheme({ 239 scheme: 'dark', 240 name: 'dim', 241 palette: invertPalette(subduedPalette), 242 options: { 243 shadowOpacity: 0.4, 244 }, 245 }) 246 247 return { 248 light, 249 dark, 250 dim, 251 } 252}