@sanity/ui
Version:
The Sanity UI components.
495 lines (441 loc) • 15.7 kB
text/typescript
import {ThemeColorBadgeTokens, ThemeColorStateTokens, ThemeConfig} from '../config'
import {
THEME_COLOR_BUTTON_MODES,
THEME_COLOR_CARD_TONES,
THEME_COLOR_STATE_TONES,
THEME_COLOR_STATES,
ThemeColorAvatar_v2,
ThemeColorAvatarColorKey,
ThemeColorAvatarHue_v2,
ThemeColorBadge_v2,
ThemeColorButton_v2,
ThemeColorButtonMode_v2,
ThemeColorButtonModeKey,
ThemeColorButtonTone_v2,
ThemeColorCard_v2,
ThemeColorCardToneKey,
ThemeColorInput_v2,
ThemeColorInputMode_v2,
ThemeColorInputState_v2,
ThemeColorScheme_v2,
ThemeColorSchemeKey,
ThemeColorSchemes_v2,
ThemeColorShadow,
ThemeColorState_v2,
ThemeColorStateKey,
ThemeColorStateToneKey,
ThemeColorSyntax,
} from '../system'
import {ColorTokenContext, resolveColorTokenValue as _color} from './colorToken'
import {resolveColorTokens} from './resolveColorTokens'
export function buildColorTheme(config?: ThemeConfig): ThemeColorSchemes_v2 {
const resolvedConfig: ThemeConfig = {
...config,
color: resolveColorTokens(config?.color),
}
return {
light: buildColorScheme({scheme: 'light'}, resolvedConfig),
dark: buildColorScheme({scheme: 'dark'}, resolvedConfig),
}
}
function buildColorScheme(
options: {scheme: ThemeColorSchemeKey},
config: ThemeConfig,
): ThemeColorScheme_v2 {
const {scheme} = options
const colorScheme = {} as ThemeColorScheme_v2
for (const tone of THEME_COLOR_CARD_TONES) {
colorScheme[tone] = buildCardColorTheme({scheme, tone}, config)
}
return colorScheme
}
function buildCardColorTheme(
options: {scheme: ThemeColorSchemeKey; tone: ThemeColorCardToneKey},
config?: ThemeConfig,
): ThemeColorCard_v2 {
const {scheme, tone} = options
const tokens = config?.color?.base?.[tone]
const hue = tokens?._hue || 'gray'
const context: ColorTokenContext = {hue, scheme}
const blendMode = tokens?._blend || ['multiply', 'screen']
return {
_blend: blendMode[scheme === 'light' ? 0 : 1],
_dark: scheme === 'dark',
accent: {
fg: _color(context, tokens?.accent?.fg),
},
avatar: buildAvatarColorTheme({scheme}, tokens),
backdrop: _color(context, tokens?.backdrop),
badge: buildBadgeColorTheme(tokens?.badge, {scheme}, config),
bg: _color(context, tokens?.bg),
border: _color(context, tokens?.border),
button: buildButtonColorTheme({scheme, tone}, config),
code: {
bg: _color(context, tokens?.code?.bg),
fg: _color(context, tokens?.code?.fg),
},
fg: _color(context, tokens?.fg),
focusRing: _color(context, tokens?.focusRing),
icon: _color(context, tokens?.icon),
input: buildInputColorTheme({scheme, tone}, config),
kbd: {
bg: _color(context, tokens?.kbd?.bg),
fg: _color(context, tokens?.kbd?.fg),
border: _color(context, tokens?.kbd?.border),
},
link: {
fg: _color(context, tokens?.link?.fg),
},
muted: {
bg: _color(context, tokens?.muted?.bg),
fg: _color(context, tokens?.muted?.fg),
},
selectable: buildSelectableColorTheme({scheme, tone}, config),
shadow: buildShadowColorTheme({scheme, tone}, config),
skeleton: {
from: _color(context, tokens?.skeleton?.from),
to: _color(context, tokens?.skeleton?.to),
},
syntax: buildSyntaxColorTheme({scheme}, config),
}
}
function buildShadowColorTheme(
options: {scheme: ThemeColorSchemeKey; tone: ThemeColorCardToneKey},
config?: ThemeConfig,
): ThemeColorShadow {
const {scheme, tone} = options
const tokens = config?.color?.base?.[tone]
const hue = tokens?._hue || 'gray'
const context: ColorTokenContext = {hue, scheme}
return {
outline: _color(context, tokens?.shadow?.outline),
umbra: _color(context, tokens?.shadow?.umbra),
penumbra: _color(context, tokens?.shadow?.penumbra),
ambient: _color(context, tokens?.shadow?.ambient),
}
}
function buildAvatarColorTheme(
options: {scheme: ThemeColorSchemeKey},
stateTokens?: ThemeColorStateTokens,
): ThemeColorAvatar_v2 {
const {scheme} = options
return {
gray: _buildAvatarColorTheme({color: 'gray', scheme}, stateTokens),
blue: _buildAvatarColorTheme({color: 'blue', scheme}, stateTokens),
purple: _buildAvatarColorTheme({color: 'purple', scheme}, stateTokens),
magenta: _buildAvatarColorTheme({color: 'magenta', scheme}, stateTokens),
red: _buildAvatarColorTheme({color: 'red', scheme}, stateTokens),
orange: _buildAvatarColorTheme({color: 'orange', scheme}, stateTokens),
yellow: _buildAvatarColorTheme({color: 'yellow', scheme}, stateTokens),
green: _buildAvatarColorTheme({color: 'green', scheme}, stateTokens),
cyan: _buildAvatarColorTheme({color: 'cyan', scheme}, stateTokens),
}
}
function _buildAvatarColorTheme(
options: {color: ThemeColorAvatarColorKey; scheme: ThemeColorSchemeKey},
stateTokens?: ThemeColorStateTokens,
): ThemeColorAvatarHue_v2 {
const {color, scheme} = options
const tokens = stateTokens?.avatar?.[color]
const context: ColorTokenContext = {hue: tokens?._hue || 'gray', scheme}
const blendMode = tokens?._blend || ['screen', 'multiply']
return {
_blend: blendMode[scheme === 'light' ? 0 : 1],
bg: _color(context, tokens?.bg),
fg: _color(context, tokens?.fg),
}
}
function buildBadgeColorTheme(
tokens: ThemeColorBadgeTokens | undefined,
options: {scheme: ThemeColorSchemeKey},
config?: ThemeConfig,
): ThemeColorBadge_v2 {
const {scheme} = options
const colorBadge = {} as ThemeColorBadge_v2
for (const tone of THEME_COLOR_STATE_TONES) {
colorBadge[tone] = _buildBadgeColorTheme(tokens, {scheme, tone}, config)
}
return colorBadge
}
function _buildBadgeColorTheme(
parentTokens: ThemeColorBadgeTokens | undefined,
options: {scheme: ThemeColorSchemeKey; tone: ThemeColorStateToneKey},
config?: ThemeConfig,
): ThemeColorBadge_v2['default'] {
const {scheme, tone} = options
const tokens = parentTokens?.[tone]
const hue = tokens?._hue || config?.color?.base?.[tone]?._hue || 'gray'
const context: ColorTokenContext = {hue, scheme}
return {
bg: _color(context, tokens?.bg),
fg: _color(context, tokens?.fg),
dot: _color(context, tokens?.dot),
icon: _color(context, tokens?.icon),
}
}
function buildButtonColorTheme(
options: {scheme: ThemeColorSchemeKey; tone: ThemeColorCardToneKey},
config?: ThemeConfig,
): ThemeColorButton_v2 {
const {scheme, tone: cardTone} = options
const modes: Partial<ThemeColorButton_v2> = {}
for (const mode of THEME_COLOR_BUTTON_MODES) {
modes[mode] = buildButtonTonesColorTheme({cardTone, scheme, mode}, config)
}
return modes as ThemeColorButton_v2
}
function buildButtonTonesColorTheme(
options: {
cardTone: ThemeColorCardToneKey
scheme: ThemeColorSchemeKey
mode: ThemeColorButtonModeKey
},
config?: ThemeConfig,
): ThemeColorButtonMode_v2 {
const {cardTone, mode, scheme} = options
const tones: Partial<ThemeColorButtonMode_v2> = {}
for (const tone of THEME_COLOR_STATE_TONES) {
tones[tone] = buildButtonStatesColorTheme({cardTone, mode, scheme, tone}, config)
}
return tones as ThemeColorButtonMode_v2
}
function buildButtonStatesColorTheme(
options: {
cardTone: ThemeColorCardToneKey
mode: ThemeColorButtonModeKey
scheme: ThemeColorSchemeKey
tone: ThemeColorStateToneKey
},
config?: ThemeConfig,
): ThemeColorButtonTone_v2 {
const {cardTone, mode, scheme, tone} = options
const states: Partial<ThemeColorButtonTone_v2> = {}
for (const state of THEME_COLOR_STATES) {
states[state] = buildButtonStateColorTheme({cardTone, mode, tone, scheme, state}, config)
}
return states as ThemeColorButtonTone_v2
}
function buildButtonStateColorTheme(
options: {
cardTone: ThemeColorCardToneKey
mode: ThemeColorButtonModeKey
tone: ThemeColorStateToneKey
scheme: ThemeColorSchemeKey
state: ThemeColorStateKey
},
config?: ThemeConfig,
): ThemeColorState_v2 {
const {cardTone, mode, tone, scheme, state} = options
const cardTokens = config?.color?.base?.[cardTone]
const tokens = config?.color?.button?.[mode]?.[tone]?.[state]
const hue = tokens?._hue || cardTokens?._hue || 'gray'
const blendMode = tokens?._blend || ['screen', 'multiply']
const context: ColorTokenContext = {hue, scheme}
return {
_blend: blendMode[scheme === 'light' ? 0 : 1],
accent: {
fg: _color(context, tokens?.accent?.fg),
},
avatar: buildAvatarColorTheme({scheme}, tokens),
badge: buildBadgeColorTheme(tokens?.badge, {scheme}, config),
bg: _color(context, tokens?.bg),
border: _color(context, tokens?.border),
code: {
bg: _color(context, tokens?.code?.bg),
fg: _color(context, tokens?.code?.fg),
},
fg: _color(context, tokens?.fg),
icon: _color(context, tokens?.icon),
muted: {
bg: _color(context, tokens?.muted?.bg),
fg: _color(context, tokens?.muted?.fg),
},
kbd: {
bg: _color(context, tokens?.kbd?.bg),
fg: _color(context, tokens?.kbd?.fg),
border: _color(context, tokens?.kbd?.border),
},
link: {
fg: _color(context, tokens?.link?.fg),
},
skeleton: {
from: _color(context, tokens?.skeleton?.from),
to: _color(context, tokens?.skeleton?.to),
},
}
}
function buildInputColorTheme(
options: {scheme: ThemeColorSchemeKey; tone: ThemeColorCardToneKey},
config?: ThemeConfig,
): ThemeColorInput_v2 {
const {scheme, tone} = options
return {
default: buildInputStatesColorTheme({mode: 'default', scheme, tone}, config),
invalid: buildInputStatesColorTheme({mode: 'invalid', scheme, tone}, config),
}
}
function buildInputStatesColorTheme(
options: {
mode: 'default' | 'invalid'
scheme: ThemeColorSchemeKey
tone: ThemeColorCardToneKey
},
config?: ThemeConfig,
): ThemeColorInputMode_v2 {
const {mode, scheme, tone} = options
return {
enabled: buildInputStateColorTheme({mode, scheme, state: 'enabled', cardTone: tone}, config),
hovered: buildInputStateColorTheme({mode, scheme, state: 'hovered', cardTone: tone}, config),
readOnly: buildInputStateColorTheme({mode, scheme, state: 'readOnly', cardTone: tone}, config),
disabled: buildInputStateColorTheme({mode, scheme, state: 'disabled', cardTone: tone}, config),
}
}
function buildInputStateColorTheme(
options: {
cardTone: ThemeColorCardToneKey
mode: 'default' | 'invalid'
scheme: ThemeColorSchemeKey
state: 'enabled' | 'hovered' | 'readOnly' | 'disabled'
},
config?: ThemeConfig,
): ThemeColorInputState_v2 {
const {cardTone, mode, scheme, state} = options
const cardTokens = config?.color?.base?.[cardTone]
const tokens = config?.color?.input?.[mode]?.[state]
const hue = tokens?._hue || cardTokens?._hue || 'gray'
const blendMode = tokens?._blend || ['screen', 'multiply']
const context: ColorTokenContext = {hue, scheme}
return {
_blend: blendMode[scheme === 'light' ? 0 : 1],
bg: _color(context, tokens?.bg),
border: _color(context, tokens?.border),
fg: _color(context, tokens?.fg),
muted: {
bg: _color(context, tokens?.muted?.bg),
},
placeholder: _color(context, tokens?.placeholder),
}
}
function buildSelectableColorTheme(
options: {
scheme: ThemeColorSchemeKey
tone: ThemeColorCardToneKey
},
config?: ThemeConfig,
): ThemeColorButtonMode_v2 {
const {scheme, tone: cardTone} = options
const tones: Partial<ThemeColorButtonMode_v2> = {}
for (const tone of THEME_COLOR_STATE_TONES) {
tones[tone] = buildSelectableStatesColorTheme({cardTone, scheme, tone}, config)
}
return tones as ThemeColorButtonMode_v2
}
function buildSelectableStatesColorTheme(
options: {
cardTone: ThemeColorCardToneKey
scheme: ThemeColorSchemeKey
tone: ThemeColorStateToneKey
},
config?: ThemeConfig,
): ThemeColorButtonTone_v2 {
const {cardTone, scheme, tone} = options
const states: Partial<ThemeColorButtonTone_v2> = {}
for (const state of THEME_COLOR_STATES) {
states[state] = buildSelectableStateColorTheme({cardTone, tone, scheme, state}, config)
}
return states as ThemeColorButtonTone_v2
}
function buildSelectableStateColorTheme(
options: {
cardTone: ThemeColorCardToneKey
scheme: ThemeColorSchemeKey
state: ThemeColorStateKey
tone: ThemeColorStateToneKey
},
config?: ThemeConfig,
): ThemeColorState_v2 {
const {cardTone, scheme, state, tone} = options
const cardTokens = config?.color?.base?.[cardTone]
const tokens = config?.color?.selectable?.[tone]?.[state]
const hue = tokens?._hue || cardTokens?._hue || 'gray'
const blendMode = tokens?._blend || ['screen', 'multiply']
const context: ColorTokenContext = {hue, scheme}
return {
_blend: blendMode[scheme === 'light' ? 0 : 1],
accent: {
fg: _color(context, tokens?.accent?.fg),
},
avatar: buildAvatarColorTheme({scheme}, tokens),
badge: buildBadgeColorTheme(tokens?.badge, {scheme}, config),
bg: _color(context, tokens?.bg),
border: _color(context, tokens?.border),
code: {
bg: _color(context, tokens?.code?.bg),
fg: _color(context, tokens?.code?.fg),
},
fg: _color(context, tokens?.fg),
icon: _color(context, tokens?.icon),
muted: {
bg: _color(context, tokens?.muted?.bg),
fg: _color(context, tokens?.muted?.fg),
},
kbd: {
bg: _color(context, tokens?.kbd?.bg),
fg: _color(context, tokens?.kbd?.fg),
border: _color(context, tokens?.kbd?.border),
},
link: {
fg: _color(context, tokens?.link?.fg),
},
skeleton: {
from: _color(context, tokens?.skeleton?.from),
to: _color(context, tokens?.skeleton?.to),
},
}
}
function buildSyntaxColorTheme(
options: {scheme: ThemeColorSchemeKey},
config?: ThemeConfig,
): ThemeColorSyntax {
const {scheme} = options
const tokens = config?.color?.syntax
const context: ColorTokenContext = {hue: 'gray', scheme}
return {
atrule: _color(context, tokens?.atrule),
attrName: _color(context, tokens?.attrName),
attrValue: _color(context, tokens?.attrValue),
attribute: _color(context, tokens?.attribute),
boolean: _color(context, tokens?.boolean),
builtin: _color(context, tokens?.builtin),
cdata: _color(context, tokens?.cdata),
char: _color(context, tokens?.char),
class: _color(context, tokens?.class),
className: _color(context, tokens?.className),
comment: _color(context, tokens?.comment),
constant: _color(context, tokens?.constant),
deleted: _color(context, tokens?.deleted),
doctype: _color(context, tokens?.doctype),
entity: _color(context, tokens?.entity),
function: _color(context, tokens?.function),
hexcode: _color(context, tokens?.hexcode),
id: _color(context, tokens?.id),
important: _color(context, tokens?.important),
inserted: _color(context, tokens?.inserted),
keyword: _color(context, tokens?.keyword),
number: _color(context, tokens?.number),
operator: _color(context, tokens?.operator),
prolog: _color(context, tokens?.prolog),
property: _color(context, tokens?.property),
pseudoClass: _color(context, tokens?.pseudoClass),
pseudoElement: _color(context, tokens?.pseudoElement),
punctuation: _color(context, tokens?.punctuation),
regex: _color(context, tokens?.regex),
selector: _color(context, tokens?.selector),
string: _color(context, tokens?.string),
symbol: _color(context, tokens?.symbol),
tag: _color(context, tokens?.tag),
unit: _color(context, tokens?.unit),
url: _color(context, tokens?.url),
variable: _color(context, tokens?.variable),
}
}