@sanity/ui
Version:
The Sanity UI components.
533 lines (475 loc) • 16.1 kB
text/typescript
import {COLOR_HUES} from '@sanity/color'
import {ThemeColorPalette, ThemeConfig} from '../config'
import {defaultColorPalette} from '../defaults/colorPalette'
import {
THEME_COLOR_CARD_TONES,
THEME_COLOR_STATE_TONES,
ThemeColorAvatar_v2,
ThemeColorBadge_v2,
ThemeColorBadgeTone_v2,
ThemeColorBlendModeKey,
ThemeColorButton_v2,
ThemeColorButtonMode_v2,
ThemeColorButtonTone_v2,
ThemeColorCard_v2,
ThemeColorInput_v2,
ThemeColorInputMode_v2,
ThemeColorInputState_v2,
ThemeColorKBD,
ThemeColorScheme_v2,
ThemeColorSchemes_v2,
ThemeColorSelectable_v2,
ThemeColorSelectableTone_v2,
ThemeColorShadow,
ThemeColorState_v2,
ThemeColorSyntax,
} from '../system'
import {defineLazyProperty} from './lib/lazy'
import {renderColorValue, RenderColorValueOptions} from './renderColorValue'
export function renderThemeColorSchemes(
value: ThemeColorSchemes_v2,
config?: ThemeConfig,
): ThemeColorSchemes_v2 {
const colorPalette = config?.palette ?? defaultColorPalette
// Lazy schemes — each scheme is only rendered when first accessed
const schemes = {} as ThemeColorSchemes_v2
defineLazyProperty(schemes, 'light', () => renderThemeColorScheme(colorPalette, value.light))
defineLazyProperty(schemes, 'dark', () => renderThemeColorScheme(colorPalette, value.dark))
return schemes
}
function renderThemeColorScheme(
colorPalette: ThemeColorPalette,
value: ThemeColorScheme_v2,
): ThemeColorScheme_v2 {
// The `default` tone must be rendered eagerly — other tones depend on its `bg`.
const renderedDefaultTone = renderThemeColor(value.default, {colorPalette})
const bg = renderedDefaultTone.bg
if (bg === 'white') {
throw new Error('Cannot blend with white background')
}
// All other tones are lazy. `transparent` and `default` are rendered without
// a `bg` option; the rest blend on top of `default`'s `bg`.
// We iterate THEME_COLOR_CARD_TONES (not Object.entries) to avoid
// force-evaluating lazy getters from the build phase.
const scheme = {default: renderedDefaultTone} as ThemeColorScheme_v2
for (const tone of THEME_COLOR_CARD_TONES) {
if (tone === 'default') continue
const opts = tone === 'transparent' ? {colorPalette} : {bg, colorPalette}
defineLazyProperty(scheme, tone, () => renderThemeColor(value[tone], opts))
}
return scheme
}
function renderThemeColor(
value: ThemeColorCard_v2,
options: {
bg?: string
colorPalette: ThemeColorPalette
},
): ThemeColorCard_v2 {
const {colorPalette, bg} = options
const blendMode = value._blend || 'multiply'
const baseBg = renderColorValue(value.bg, {colorPalette, bg, blendMode})
const colorOptions: RenderColorValueOptions = {colorPalette, bg: baseBg, blendMode}
const button = renderThemeColorButton(value.button, {
baseBg,
blendMode,
colorPalette,
})
const selectable = renderThemeColorSelectable(value.selectable, {
colorPalette,
baseBg,
blendMode,
})
const shadow: ThemeColorShadow = {
outline: renderColorValue(value.shadow.outline, colorOptions),
umbra: renderColorValue(value.shadow.umbra, {
...colorOptions,
bg: undefined,
colorPalette: {...colorPalette, black: '#000000'},
}),
penumbra: renderColorValue(value.shadow.penumbra, {
...colorOptions,
bg: undefined,
colorPalette: {...colorPalette, black: '#000000'},
}),
ambient: renderColorValue(value.shadow.ambient, {
...colorOptions,
bg: undefined,
colorPalette: {...colorPalette, black: '#000000'},
}),
}
return {
_blend: blendMode,
_dark: value._dark,
accent: {
fg: renderColorValue(value.accent.fg, colorOptions),
},
avatar: renderThemeColorAvatar(value.avatar, {baseBg, colorPalette, blendMode}),
backdrop: renderColorValue(value.backdrop, colorOptions),
badge: renderThemeColorBadge(value.badge, {baseBg, colorPalette, blendMode}),
bg: baseBg,
border: renderColorValue(value.border, colorOptions),
button,
code: {
bg: renderColorValue(value.code.bg, colorOptions),
fg: renderColorValue(value.code.fg, colorOptions),
},
fg: renderColorValue(value.fg, colorOptions),
focusRing: renderColorValue(value.focusRing, colorOptions),
icon: renderColorValue(value.icon, colorOptions),
input: renderThemeColorInput(value.input, {baseBg, colorPalette, blendMode}),
kbd: renderThemeColorKBD(value.kbd, {baseBg, colorPalette, blendMode}),
link: {
fg: renderColorValue(value.link.fg, colorOptions),
},
muted: {
bg: renderColorValue(value.muted.bg, colorOptions),
fg: renderColorValue(value.muted.fg, colorOptions),
},
shadow,
skeleton: {
from: renderColorValue(value.skeleton.from, colorOptions),
to: renderColorValue(value.skeleton.to, colorOptions),
},
syntax: renderSyntaxColorTheme(value.syntax, {baseBg, colorPalette, blendMode}),
selectable,
}
}
function renderThemeColorKBD(
value: ThemeColorKBD,
options: {
baseBg: string
blendMode: ThemeColorBlendModeKey
colorPalette: ThemeColorPalette
},
): ThemeColorKBD {
const {baseBg, blendMode, colorPalette} = options
const rootOptions: RenderColorValueOptions = {
bg: baseBg,
blendMode,
colorPalette,
}
const bg = renderColorValue(value.bg, rootOptions)
const colorOptions: RenderColorValueOptions = {
bg,
blendMode,
colorPalette,
}
return {
bg,
fg: renderColorValue(value.fg, colorOptions),
border: renderColorValue(value.border, colorOptions),
}
}
function renderThemeColorAvatar(
value: ThemeColorAvatar_v2,
options: {
baseBg: string
blendMode: ThemeColorBlendModeKey
colorPalette: ThemeColorPalette
},
): ThemeColorAvatar_v2 {
const colorAvatar = {} as ThemeColorAvatar_v2
for (const hue of COLOR_HUES) {
colorAvatar[hue] = renderThemeColorAvatarColor(value[hue], options)
}
return colorAvatar
}
function renderThemeColorAvatarColor(
value: ThemeColorAvatar_v2['gray'],
options: {
baseBg: string
blendMode: ThemeColorBlendModeKey
colorPalette: ThemeColorPalette
},
): ThemeColorAvatar_v2['gray'] {
const {baseBg, blendMode: rootBlendMode, colorPalette} = options
const blendMode = value._blend || 'multiply'
const rootOptions: RenderColorValueOptions = {
bg: baseBg,
blendMode: rootBlendMode,
colorPalette,
}
const bg = renderColorValue(value.bg, rootOptions)
const colorOptions: RenderColorValueOptions = {
bg,
blendMode,
colorPalette,
}
return {
_blend: blendMode,
bg,
fg: renderColorValue(value.fg, colorOptions),
}
}
function renderThemeColorBadge(
value: ThemeColorBadge_v2,
options: {
baseBg: string
blendMode: ThemeColorBlendModeKey
colorPalette: ThemeColorPalette
},
): ThemeColorBadge_v2 {
const colorBadge = {} as ThemeColorBadge_v2
for (const tone of THEME_COLOR_STATE_TONES) {
colorBadge[tone] = renderThemeColorBadgeColor(value[tone], options)
}
return colorBadge
}
function renderThemeColorBadgeColor(
value: ThemeColorBadgeTone_v2,
options: {
baseBg: string
blendMode: ThemeColorBlendModeKey
colorPalette: ThemeColorPalette
},
): ThemeColorBadgeTone_v2 {
const {baseBg, blendMode: rootBlendMode, colorPalette} = options
const blendMode = rootBlendMode
const rootOptions: RenderColorValueOptions = {
bg: baseBg,
blendMode: rootBlendMode,
colorPalette,
}
const bg = renderColorValue(value.bg, rootOptions)
const colorOptions: RenderColorValueOptions = {
bg,
blendMode,
colorPalette,
}
return {
bg,
dot: renderColorValue(value.dot, colorOptions),
fg: renderColorValue(value.fg, colorOptions),
icon: renderColorValue(value.icon, colorOptions),
}
}
function renderThemeColorButton(
value: ThemeColorButton_v2,
options: {
baseBg: string
blendMode: ThemeColorBlendModeKey
colorPalette: ThemeColorPalette
},
): ThemeColorButton_v2 {
return {
default: renderThemeColorButtonTones(value.default, options),
ghost: renderThemeColorButtonTones(value.ghost, options),
bleed: renderThemeColorButtonTones(value.bleed, options),
}
}
function renderThemeColorButtonTones(
value: ThemeColorButtonMode_v2,
options: {
baseBg: string
blendMode: ThemeColorBlendModeKey
colorPalette: ThemeColorPalette
},
): ThemeColorButtonMode_v2 {
const colorButtonMode = {} as ThemeColorButtonMode_v2
for (const tone of THEME_COLOR_STATE_TONES) {
colorButtonMode[tone] = renderThemeColorButtonStates(value[tone], options)
}
return colorButtonMode
}
function renderThemeColorButtonStates(
value: ThemeColorButtonTone_v2,
options: {
baseBg: string
blendMode: ThemeColorBlendModeKey
colorPalette: ThemeColorPalette
},
): ThemeColorButtonTone_v2 {
return {
enabled: renderThemeColorState(value.enabled, options),
hovered: renderThemeColorState(value.hovered, options),
pressed: renderThemeColorState(value.pressed, options),
selected: renderThemeColorState(value.selected, options),
disabled: renderThemeColorState(value.disabled, options),
}
}
function renderThemeColorState(
value: ThemeColorState_v2,
options: {
baseBg: string
blendMode: ThemeColorBlendModeKey
colorPalette: ThemeColorPalette
},
): ThemeColorState_v2 {
const {baseBg, blendMode: rootBlendMode, colorPalette} = options
const blendMode = value._blend || 'multiply'
const rootOptions: RenderColorValueOptions = {
bg: baseBg,
blendMode: rootBlendMode,
colorPalette,
}
const bg = renderColorValue(value.bg, rootOptions)
const colorOptions: RenderColorValueOptions = {
bg,
blendMode,
colorPalette,
}
return {
_blend: blendMode,
accent: {
fg: renderColorValue(value.accent.fg, colorOptions),
},
avatar: renderThemeColorAvatar(value.avatar, {baseBg: bg, colorPalette, blendMode}),
badge: renderThemeColorBadge(value.badge, {baseBg: bg, colorPalette, blendMode}),
bg,
border: renderColorValue(value.border, colorOptions),
code: {
bg: renderColorValue(value.code.bg, colorOptions),
fg: renderColorValue(value.code.fg, colorOptions),
},
fg: renderColorValue(value.fg, colorOptions),
icon: renderColorValue(value.icon, colorOptions),
link: {
fg: renderColorValue(value.link.fg, colorOptions),
},
muted: {
bg: renderColorValue(value.muted.bg, colorOptions),
fg: renderColorValue(value.muted.fg, colorOptions),
},
kbd: {
bg: renderColorValue(value.kbd.bg, colorOptions),
fg: renderColorValue(value.kbd.fg, colorOptions),
border: renderColorValue(value.kbd.border, colorOptions),
},
skeleton: {
from: renderColorValue(value.skeleton?.from, colorOptions),
to: renderColorValue(value.skeleton?.to, colorOptions),
},
}
}
function renderThemeColorInput(
value: ThemeColorInput_v2,
options: {
baseBg: string
blendMode: ThemeColorBlendModeKey
colorPalette: ThemeColorPalette
},
): ThemeColorInput_v2 {
return {
default: renderInputStatesColorTheme(value.default, options),
invalid: renderInputStatesColorTheme(value.invalid, options),
}
}
function renderInputStatesColorTheme(
value: ThemeColorInputMode_v2,
options: {
baseBg: string
blendMode: ThemeColorBlendModeKey
colorPalette: ThemeColorPalette
},
): ThemeColorInputMode_v2 {
return {
enabled: renderInputStateColorTheme(value.enabled, options),
hovered: renderInputStateColorTheme(value.hovered, options),
readOnly: renderInputStateColorTheme(value.readOnly, options),
disabled: renderInputStateColorTheme(value.disabled, options),
}
}
function renderInputStateColorTheme(
value: ThemeColorInputState_v2,
options: {
baseBg: string
blendMode: ThemeColorBlendModeKey
colorPalette: ThemeColorPalette
},
): ThemeColorInputState_v2 {
const {baseBg, blendMode: rootBlendMode, colorPalette} = options
const blendMode = value._blend || 'multiply'
const rootOptions: RenderColorValueOptions = {colorPalette, bg: baseBg, blendMode: rootBlendMode}
const bg = renderColorValue(value.bg, rootOptions)
const colorOptions: RenderColorValueOptions = {colorPalette, bg, blendMode}
return {
_blend: blendMode,
bg,
border: renderColorValue(value.border, colorOptions),
fg: renderColorValue(value.fg, colorOptions),
muted: {
bg: renderColorValue(value.muted.bg, colorOptions),
},
placeholder: renderColorValue(value.placeholder, colorOptions),
}
}
function renderThemeColorSelectable(
value: ThemeColorSelectable_v2,
options: {
baseBg: string
blendMode: ThemeColorBlendModeKey
colorPalette: ThemeColorPalette
},
): ThemeColorSelectable_v2 {
const colorSelectable = {} as ThemeColorSelectable_v2
for (const tone of THEME_COLOR_STATE_TONES) {
colorSelectable[tone] = renderThemeColorSelectableStates(value[tone], options)
}
return colorSelectable
}
function renderThemeColorSelectableStates(
value: ThemeColorSelectableTone_v2,
options: {
baseBg: string
blendMode: ThemeColorBlendModeKey
colorPalette: ThemeColorPalette
},
): ThemeColorSelectableTone_v2 {
return {
enabled: renderThemeColorState(value.enabled, options),
hovered: renderThemeColorState(value.hovered, options),
pressed: renderThemeColorState(value.pressed, options),
selected: renderThemeColorState(value.selected, options),
disabled: renderThemeColorState(value.disabled, options),
}
}
function renderSyntaxColorTheme(
value: ThemeColorSyntax,
options: {
baseBg: string
blendMode: ThemeColorBlendModeKey
colorPalette: ThemeColorPalette
},
): ThemeColorSyntax {
const {colorPalette, baseBg, blendMode} = options
const colorOptions: RenderColorValueOptions = {colorPalette, bg: baseBg, blendMode}
return {
atrule: renderColorValue(value.atrule, colorOptions),
attrName: renderColorValue(value.attrName, colorOptions),
attrValue: renderColorValue(value.attrValue, colorOptions),
attribute: renderColorValue(value.attribute, colorOptions),
boolean: renderColorValue(value.boolean, colorOptions),
builtin: renderColorValue(value.builtin, colorOptions),
cdata: renderColorValue(value.cdata, colorOptions),
char: renderColorValue(value.char, colorOptions),
class: renderColorValue(value.class, colorOptions),
className: renderColorValue(value.className, colorOptions),
comment: renderColorValue(value.comment, colorOptions),
constant: renderColorValue(value.constant, colorOptions),
deleted: renderColorValue(value.deleted, colorOptions),
doctype: renderColorValue(value.doctype, colorOptions),
entity: renderColorValue(value.entity, colorOptions),
function: renderColorValue(value.function, colorOptions),
hexcode: renderColorValue(value.hexcode, colorOptions),
id: renderColorValue(value.id, colorOptions),
important: renderColorValue(value.important, colorOptions),
inserted: renderColorValue(value.inserted, colorOptions),
keyword: renderColorValue(value.keyword, colorOptions),
number: renderColorValue(value.number, colorOptions),
operator: renderColorValue(value.operator, colorOptions),
prolog: renderColorValue(value.prolog, colorOptions),
property: renderColorValue(value.property, colorOptions),
pseudoClass: renderColorValue(value.pseudoClass, colorOptions),
pseudoElement: renderColorValue(value.pseudoElement, colorOptions),
punctuation: renderColorValue(value.punctuation, colorOptions),
regex: renderColorValue(value.regex, colorOptions),
selector: renderColorValue(value.selector, colorOptions),
string: renderColorValue(value.string, colorOptions),
symbol: renderColorValue(value.symbol, colorOptions),
tag: renderColorValue(value.tag, colorOptions),
unit: renderColorValue(value.unit, colorOptions),
url: renderColorValue(value.url, colorOptions),
variable: renderColorValue(value.variable, colorOptions),
}
}