Skip to content

Instantly share code, notes, and snippets.

@nandorojo
Created May 1, 2023 22:40
Show Gist options
  • Save nandorojo/6501cd460c16899160ef59f6e74eb1b4 to your computer and use it in GitHub Desktop.
Save nandorojo/6501cd460c16899160ef59f6e74eb1b4 to your computer and use it in GitHub Desktop.
import {
MaskOptions,
addChildren,
applyMask,
createStrengthenMask,
createTheme,
createWeakenMask,
} from '@tamagui/create-theme'
import { mauve, slate, mauveDark, slateDark } from '@tamagui/colors'
import { colorTokens } from '@tamagui/themes'
import { keys } from '@beatgig/helpers/object'
export const darkColors = {
...colorTokens.dark.blue,
...colorTokens.dark.gray,
...colorTokens.dark.green,
...colorTokens.dark.orange,
...colorTokens.dark.pink,
...colorTokens.dark.purple,
...colorTokens.dark.red,
...colorTokens.dark.yellow,
}
export const lightColors = {
...colorTokens.light.blue,
...colorTokens.light.gray,
...colorTokens.light.green,
...colorTokens.light.orange,
...colorTokens.light.pink,
...colorTokens.light.purple,
...colorTokens.light.red,
...colorTokens.light.yellow,
}
type ColorName = keyof typeof colorTokens.dark
const lightTransparent = 'rgba(255,255,255,0)'
const darkTransparent = 'rgba(10,10,10,0)'
// background => foreground
const palettes = {
light: [
lightTransparent, // transparent
mauve.mauve1,
mauve.mauve2,
slate.slate3,
slate.slate4,
slate.slate5,
slate.slate6,
slate.slate7,
slate.slate8,
slate.slate9,
slate.slate10,
slate.slate11,
slate.slate12,
darkTransparent,
'black', // contrast foreground
'white', // background strong
],
dark: [
darkTransparent,
mauveDark.mauve1,
mauveDark.mauve2,
slateDark.slate3,
slateDark.slate4,
slateDark.slate5,
slateDark.slate6,
slateDark.slate7,
slateDark.slate8,
slateDark.slate9,
slateDark.slate10,
slateDark.slate11,
slateDark.slate12,
lightTransparent,
'white',
'black',
],
}
const templateColors = {
color1: 1,
color2: 2,
color3: 3,
color4: 4,
color5: 5,
color6: 6,
color7: 7,
color8: 8,
color9: 9,
color10: 10,
color11: 11,
color12: 12,
}
const templateShadows = {
shadowColor: 1,
shadowColorHover: 1,
shadowColorPress: 2,
shadowColorFocus: 2,
}
// we can use subset of our template as a "skip" so it doesn't get adjusted with masks
const skip = {
...templateColors,
...templateShadows,
}
// templates use the palette and specify index
// negative goes backwards from end so -1 is the last item
const template = {
...skip,
// the background, color, etc keys here work like generics - they make it so you
// can publish components for others to use without mandating a specific color scale
// the @tamagui/button Button component looks for `$background`, so you set the
// dark_red_Button theme to have a stronger background than the dark_red theme.
background: 2,
backgroundHover: 3,
backgroundPress: 1,
backgroundFocus: 2,
backgroundStrong: -0,
backgroundTransparent: 0,
color: -3,
colorHover: -4,
colorPress: -3,
colorFocus: -4,
colorTransparent: -2,
borderColor: 4,
borderColorHover: 5,
borderColorPress: 3,
borderColorFocus: 4,
placeholderColor: -7,
contrastColor: -1,
}
const lightShadowColor = 'rgba(0,0,0,0.02)'
const lightShadowColorStrong = 'rgba(0,0,0,0.066)'
const darkShadowColor = 'rgba(0,0,0,0.2)'
const darkShadowColorStrong = 'rgba(0,0,0,0.3)'
const lightShadows = {
shadowColor: lightShadowColorStrong,
shadowColorHover: lightShadowColorStrong,
shadowColorPress: lightShadowColor,
shadowColorFocus: lightShadowColor,
}
const darkShadows = {
shadowColor: darkShadowColorStrong,
shadowColorHover: darkShadowColorStrong,
shadowColorPress: darkShadowColor,
shadowColorFocus: darkShadowColor,
}
const lightTemplate = {
...template,
// our light color palette is... a bit unique
borderColor: 6,
borderColorHover: 7,
borderColorFocus: 5,
borderColorPress: 6,
...lightShadows,
}
const darkTemplate = { ...template, ...darkShadows }
const light = createTheme(palettes.light, lightTemplate)
const dark = createTheme(palettes.dark, darkTemplate)
type SubTheme = typeof light
const baseThemes: {
light: SubTheme
dark: SubTheme
} = {
light,
dark,
}
const masks = {
weaker: createWeakenMask(),
stronger: createStrengthenMask(),
}
// default mask options for most uses
const maskOptions: MaskOptions = {
skip,
// avoids the transparent ends
max: palettes.light.length - 2,
min: 1,
}
const allThemes = addChildren(baseThemes, (name, theme) => {
const isLight = name === 'light'
const inverseName = isLight ? 'dark' : 'light'
const inverseTheme = baseThemes[inverseName]
const transparent = (hsl: string, opacity = 0) =>
hsl.replace(`%)`, `%, ${opacity})`).replace(`hsl(`, `hsla(`)
// setup colorThemes and their inverses
const [colorThemes, inverseColorThemes] = [
colorTokens[name],
colorTokens[inverseName],
].map((colorSet) => {
return Object.fromEntries(
keys(colorSet).map((color) => {
const colorPalette = Object.values(colorSet[color]) as string[]
// were re-ordering these
const [head, tail] = [colorPalette.slice(0, 6), colorPalette.slice(6)]
const contrasts: Record<typeof color, 'black' | 'white'> = {
blue: 'white',
gray: 'white',
green: 'white',
orange: 'white',
pink: 'white',
purple: 'white',
red: 'white',
yellow: 'black',
}
// add our transparent colors first/last
// and make sure the last (foreground) color is white/black rather than colorful
// this is mostly for consistency with the older theme-base
const palette = [
transparent(colorPalette[0]),
...head,
...tail,
theme.color,
transparent(colorPalette[colorPalette.length - 1]),
contrasts[color], // contrastColor
isLight ? 'white' : 'black', // backgroundStrong
]
const colorTheme = createTheme(
palette,
isLight
? {
...lightTemplate,
// light color themes are a bit less sensitive
borderColor: 4,
borderColorHover: 5,
borderColorFocus: 4,
borderColorPress: 4,
}
: darkTemplate
)
return [color, colorTheme]
})
) as Record<ColorName, SubTheme>
})
const allColorThemes = addChildren(colorThemes, (colorName, colorTheme) => {
const inverse = inverseColorThemes[colorName]
return {
...getAltThemes(colorTheme, inverse),
...getComponentThemes(colorTheme, inverse),
}
})
const baseActiveTheme = applyMask(colorThemes.blue, masks.weaker, {
...maskOptions,
strength: 4,
})
const baseSubThemes = {
...getAltThemes(theme, inverseTheme, baseActiveTheme),
...getComponentThemes(theme, inverseTheme),
}
return {
...baseSubThemes,
...allColorThemes,
}
function getAltThemes(
theme: SubTheme,
inverse: SubTheme,
activeTheme?: SubTheme
) {
const maskOptionsAlt = {
...maskOptions,
skip: templateShadows,
}
const alt1 = applyMask(theme, masks.weaker, maskOptionsAlt)
const alt2 = applyMask(alt1, masks.weaker, maskOptionsAlt)
const active =
activeTheme ??
applyMask(theme, masks.weaker, {
...maskOptions,
strength: 4,
})
return addChildren({ alt1, alt2, active }, (_, subTheme) => {
return getComponentThemes(
subTheme,
subTheme === inverse ? theme : inverse
)
})
}
function getComponentThemes(theme: SubTheme, inverse: SubTheme) {
const weaker1 = applyMask(theme, masks.weaker, maskOptions)
const weaker2 = applyMask(weaker1, masks.weaker, maskOptions)
const stronger1 = applyMask(theme, masks.stronger, maskOptions)
const inverse1 = applyMask(inverse, masks.weaker, maskOptions)
const inverse2 = applyMask(inverse1, masks.weaker, maskOptions)
const strongerBorderLighterBackground: SubTheme = isLight
? {
...stronger1,
borderColor: weaker1.borderColor,
borderColorHover: weaker1.borderColorHover,
borderColorPress: weaker1.borderColorPress,
borderColorFocus: weaker1.borderColorFocus,
}
: {
...theme,
borderColor: weaker1.borderColor,
borderColorHover: weaker1.borderColorHover,
borderColorPress: weaker1.borderColorPress,
borderColorFocus: weaker1.borderColorFocus,
}
const overlayTheme = {
background: isLight ? 'rgba(0,0,0,0.5)' : 'rgba(255,255,255,0.9)',
}
return {
Card: weaker1,
Button: weaker2,
Checkbox: weaker2,
DrawerFrame: weaker1,
SliderTrack: stronger1,
SliderTrackActive: weaker2,
SliderThumb: inverse1,
Progress: weaker1,
ProgressIndicator: inverse,
Switch: weaker2,
SwitchThumb: inverse2,
TooltipArrow: weaker1,
TooltipContent: weaker2,
Input: strongerBorderLighterBackground,
TextArea: strongerBorderLighterBackground,
Tooltip: inverse1,
SheetOverlay: overlayTheme,
DialogOverlay: overlayTheme,
ModalOverlay: overlayTheme,
}
}
})
export const themesNew = {
...allThemes,
// bring back the full type, the rest use a subset to avoid clogging up ts,
// tamagui will be smart and use the top level themes as the type for useTheme() etc
light: createTheme(palettes.light, lightTemplate, {
nonInheritedValues: lightColors,
}),
dark: createTheme(palettes.dark, darkTemplate, {
nonInheritedValues: darkColors,
}),
}
// if (process.env.NODE_ENV === 'development') {
// console.log(JSON.stringify(themes).length)
// }
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment