@sanity/ui
Version:
The Sanity UI components.
533 lines (476 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_STATE_TONES,
ThemeColorAvatar_v2,
ThemeColorBadge_v2,
ThemeColorBadgeTone_v2,
ThemeColorBlendModeKey,
ThemeColorButton_v2,
ThemeColorButtonMode_v2,
ThemeColorButtonTone_v2,
ThemeColorCard_v2,
ThemeColorCardToneKey,
ThemeColorInput_v2,
ThemeColorInputMode_v2,
ThemeColorInputState_v2,
ThemeColorKBD,
ThemeColorScheme_v2,
ThemeColorSchemes_v2,
ThemeColorSelectable_v2,
ThemeColorSelectableTone_v2,
ThemeColorShadow,
ThemeColorState_v2,
ThemeColorSyntax,
} from '../system'
import {renderColorValue, RenderColorValueOptions} from './renderColorValue'
export function renderThemeColorSchemes(
value: ThemeColorSchemes_v2,
config?: ThemeConfig,
): ThemeColorSchemes_v2 {
const colorPalette = config?.palette ?? defaultColorPalette
return {
light: renderThemeColorScheme(colorPalette, value.light),
dark: renderThemeColorScheme(colorPalette, value.dark),
}
}
function renderThemeColorScheme(
colorPalette: ThemeColorPalette,
value: ThemeColorScheme_v2,
): ThemeColorScheme_v2 {
const toneEntries = Object.entries(value) as [ThemeColorCardToneKey, ThemeColorCard_v2][]
const [, transparentTone] = toneEntries.find(([k]) => k === 'transparent')!
const [, defaultTone] = toneEntries.find(([k]) => k === 'default')!
// The `transparent` and `default` tones are special cases, so we render them first
// (rendered without a `bg` option).
// But the rest of the tones are rendered on top of the `default` tone's `bg`.
const renderedTransparentTone = renderThemeColor(transparentTone, {colorPalette})
const renderedDefaultTone = renderThemeColor(defaultTone, {colorPalette})
// Get the `default` tone's `bg` property
const bg = renderedDefaultTone.bg
if (bg === 'white') {
throw new Error('Cannot blend with white background')
}
return Object.fromEntries([
['transparent', renderedTransparentTone],
['default', renderedDefaultTone],
...toneEntries
.filter(([k]) => k !== 'default' && k !== 'transparent')
.map(([k, v]) => [k, renderThemeColor(v, {bg, colorPalette})]),
]) as ThemeColorScheme_v2
}
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),
}
}