logo

pleroma-fe

My custom branche(s) on git.pleroma.social/pleroma/pleroma-fe git clone https://hacktivis.me/git/pleroma-fe.git

theme_data.service.js (21699B)


  1. import { convert, brightness, contrastRatio } from 'chromatism'
  2. import { rgb2hex, rgba2css, alphaBlendLayers, getTextColor, relativeLuminance, getCssColor } from '../color_convert/color_convert.js'
  3. import { LAYERS, DEFAULT_OPACITY, SLOT_INHERITANCE } from './pleromafe.js'
  4. /*
  5. * # What's all this?
  6. * Here be theme engine for pleromafe. All of this supposed to ease look
  7. * and feel customization, making widget styles and make developer's life
  8. * easier when it comes to supporting themes. Like many other theme systems
  9. * it operates on color definitions, or "slots" - for example you define
  10. * "button" color slot and then in UI component Button's CSS you refer to
  11. * it as a CSS3 Variable.
  12. *
  13. * Some applications allow you to customize colors for certain things.
  14. * Some UI toolkits allow you to define colors for each type of widget.
  15. * Most of them are pretty barebones and have no assistance for common
  16. * problems and cases, and in general themes themselves are very hard to
  17. * maintain in all aspects. This theme engine tries to solve all of the
  18. * common problems with themes.
  19. *
  20. * You don't have redefine several similar colors if you just want to
  21. * change one color - all color slots are derived from other ones, so you
  22. * can have at least one or two "basic" colors defined and have all other
  23. * components inherit and modify basic ones.
  24. *
  25. * You don't have to test contrast ratio for colors or pick text color for
  26. * each element even if you have light-on-dark elements in dark-on-light
  27. * theme.
  28. *
  29. * You don't have to maintain order of code for inheriting slots from othet
  30. * slots - dependency graph resolving does it for you.
  31. */
  32. /* This indicates that this version of code outputs similar theme data and
  33. * should be incremented if output changes - for instance if getTextColor
  34. * function changes and older themes no longer render text colors as
  35. * author intended previously.
  36. */
  37. export const CURRENT_VERSION = 3
  38. export const getLayersArray = (layer, data = LAYERS) => {
  39. const array = [layer]
  40. let parent = data[layer]
  41. while (parent) {
  42. array.unshift(parent)
  43. parent = data[parent]
  44. }
  45. return array
  46. }
  47. export const getLayers = (layer, variant = layer, opacitySlot, colors, opacity) => {
  48. return getLayersArray(layer).map((currentLayer) => ([
  49. currentLayer === layer
  50. ? colors[variant]
  51. : colors[currentLayer],
  52. currentLayer === layer
  53. ? opacity[opacitySlot] || 1
  54. : opacity[currentLayer]
  55. ]))
  56. }
  57. const getDependencies = (key, inheritance) => {
  58. const data = inheritance[key]
  59. if (typeof data === 'string' && data.startsWith('--')) {
  60. return [data.substring(2)]
  61. } else {
  62. if (data === null) return []
  63. const { depends, layer, variant } = data
  64. const layerDeps = layer
  65. ? getLayersArray(layer).map(currentLayer => {
  66. return currentLayer === layer
  67. ? variant || layer
  68. : currentLayer
  69. })
  70. : []
  71. if (Array.isArray(depends)) {
  72. return [...depends, ...layerDeps]
  73. } else {
  74. return [...layerDeps]
  75. }
  76. }
  77. }
  78. /**
  79. * Sorts inheritance object topologically - dependant slots come after
  80. * dependencies
  81. *
  82. * @property {Object} inheritance - object defining the nodes
  83. * @property {Function} getDeps - function that returns dependencies for
  84. * given value and inheritance object.
  85. * @returns {String[]} keys of inheritance object, sorted in topological
  86. * order. Additionally, dependency-less nodes will always be first in line
  87. */
  88. export const topoSort = (
  89. inheritance = SLOT_INHERITANCE,
  90. getDeps = getDependencies
  91. ) => {
  92. // This is an implementation of https://en.wikipedia.org/wiki/Tarjan%27s_strongly_connected_components_algorithm
  93. const allKeys = Object.keys(inheritance)
  94. const whites = new Set(allKeys)
  95. const grays = new Set()
  96. const blacks = new Set()
  97. const unprocessed = [...allKeys]
  98. const output = []
  99. const step = (node) => {
  100. if (whites.has(node)) {
  101. // Make node "gray"
  102. whites.delete(node)
  103. grays.add(node)
  104. // Do step for each node connected to it (one way)
  105. getDeps(node, inheritance).forEach(step)
  106. // Make node "black"
  107. grays.delete(node)
  108. blacks.add(node)
  109. // Put it into the output list
  110. output.push(node)
  111. } else if (grays.has(node)) {
  112. console.debug('Cyclic depenency in topoSort, ignoring')
  113. output.push(node)
  114. } else if (blacks.has(node)) {
  115. // do nothing
  116. } else {
  117. throw new Error('Unintended condition in topoSort!')
  118. }
  119. }
  120. while (unprocessed.length > 0) {
  121. step(unprocessed.pop())
  122. }
  123. // The index thing is to make sorting stable on browsers
  124. // where Array.sort() isn't stable
  125. return output.map((data, index) => ({ data, index })).sort(({ data: a, index: ai }, { data: b, index: bi }) => {
  126. const depsA = getDeps(a, inheritance).length
  127. const depsB = getDeps(b, inheritance).length
  128. if (depsA === depsB || (depsB !== 0 && depsA !== 0)) return ai - bi
  129. if (depsA === 0 && depsB !== 0) return -1
  130. if (depsB === 0 && depsA !== 0) return 1
  131. return 0 // failsafe, shouldn't happen?
  132. }).map(({ data }) => data)
  133. }
  134. const expandSlotValue = (value) => {
  135. if (typeof value === 'object') return value
  136. return {
  137. depends: value.startsWith('--') ? [value.substring(2)] : [],
  138. default: value.startsWith('#') ? value : undefined
  139. }
  140. }
  141. /**
  142. * retrieves opacity slot for given slot. This goes up the depenency graph
  143. * to find which parent has opacity slot defined for it.
  144. * TODO refactor this
  145. */
  146. export const getOpacitySlot = (
  147. k,
  148. inheritance = SLOT_INHERITANCE,
  149. getDeps = getDependencies
  150. ) => {
  151. const value = expandSlotValue(inheritance[k])
  152. if (value.opacity === null) return
  153. if (value.opacity) return value.opacity
  154. const findInheritedOpacity = (key, visited = [k]) => {
  155. const depSlot = getDeps(key, inheritance)[0]
  156. if (depSlot === undefined) return
  157. const dependency = inheritance[depSlot]
  158. if (dependency === undefined) return
  159. if (dependency.opacity || dependency === null) {
  160. return dependency.opacity
  161. } else if (dependency.depends && visited.includes(depSlot)) {
  162. return findInheritedOpacity(depSlot, [...visited, depSlot])
  163. } else {
  164. return null
  165. }
  166. }
  167. if (value.depends) {
  168. return findInheritedOpacity(k)
  169. }
  170. }
  171. /**
  172. * retrieves layer slot for given slot. This goes up the depenency graph
  173. * to find which parent has opacity slot defined for it.
  174. * this is basically copypaste of getOpacitySlot except it checks if key is
  175. * in LAYERS
  176. * TODO refactor this
  177. */
  178. export const getLayerSlot = (
  179. k,
  180. inheritance = SLOT_INHERITANCE,
  181. getDeps = getDependencies
  182. ) => {
  183. const value = expandSlotValue(inheritance[k])
  184. if (LAYERS[k]) return k
  185. if (value.layer === null) return
  186. if (value.layer) return value.layer
  187. const findInheritedLayer = (key, visited = [k]) => {
  188. const depSlot = getDeps(key, inheritance)[0]
  189. if (depSlot === undefined) return
  190. const dependency = inheritance[depSlot]
  191. if (dependency === undefined) return
  192. if (dependency.layer || dependency === null) {
  193. return dependency.layer
  194. } else if (dependency.depends) {
  195. return findInheritedLayer(dependency, [...visited, depSlot])
  196. } else {
  197. return null
  198. }
  199. }
  200. if (value.depends) {
  201. return findInheritedLayer(k)
  202. }
  203. }
  204. /**
  205. * topologically sorted SLOT_INHERITANCE
  206. */
  207. export const SLOT_ORDERED = topoSort(
  208. Object.entries(SLOT_INHERITANCE)
  209. .sort(([aK, aV], [bK, bV]) => ((aV && aV.priority) || 0) - ((bV && bV.priority) || 0))
  210. .reduce((acc, [k, v]) => ({ ...acc, [k]: v }), {})
  211. )
  212. /**
  213. * All opacity slots used in color slots, their default values and affected
  214. * color slots.
  215. */
  216. export const OPACITIES = Object.entries(SLOT_INHERITANCE).reduce((acc, [k, v]) => {
  217. const opacity = getOpacitySlot(k, SLOT_INHERITANCE, getDependencies)
  218. if (opacity) {
  219. return {
  220. ...acc,
  221. [opacity]: {
  222. defaultValue: DEFAULT_OPACITY[opacity] || 1,
  223. affectedSlots: [...((acc[opacity] && acc[opacity].affectedSlots) || []), k]
  224. }
  225. }
  226. } else {
  227. return acc
  228. }
  229. }, {})
  230. /**
  231. * Handle dynamic color
  232. */
  233. export const computeDynamicColor = (sourceColor, getColor, mod) => {
  234. if (typeof sourceColor !== 'string' || !sourceColor.startsWith('--')) return sourceColor
  235. let targetColor = null
  236. // Color references other color
  237. const [variable, modifier] = sourceColor.split(/,/g).map(str => str.trim())
  238. const variableSlot = variable.substring(2)
  239. targetColor = getColor(variableSlot)
  240. if (modifier) {
  241. targetColor = brightness(Number.parseFloat(modifier) * mod, targetColor).rgb
  242. }
  243. return targetColor
  244. }
  245. /**
  246. * THE function you want to use. Takes provided colors and opacities
  247. * value and uses inheritance data to figure out color needed for the slot.
  248. */
  249. export const getColors = (sourceColors, sourceOpacity) => SLOT_ORDERED.reduce(({ colors, opacity }, key) => {
  250. const sourceColor = sourceColors[key]
  251. const value = expandSlotValue(SLOT_INHERITANCE[key])
  252. const deps = getDependencies(key, SLOT_INHERITANCE)
  253. const isTextColor = !!value.textColor
  254. const variant = value.variant || value.layer
  255. let backgroundColor = null
  256. if (isTextColor) {
  257. backgroundColor = alphaBlendLayers(
  258. { ...(colors[deps[0]] || convert(sourceColors[key] || '#FF00FF').rgb) },
  259. getLayers(
  260. getLayerSlot(key) || 'bg',
  261. variant || 'bg',
  262. getOpacitySlot(variant),
  263. colors,
  264. opacity
  265. )
  266. )
  267. } else if (variant && variant !== key) {
  268. backgroundColor = colors[variant] || convert(sourceColors[variant]).rgb
  269. } else {
  270. backgroundColor = colors.bg || convert(sourceColors.bg)
  271. }
  272. const isLightOnDark = relativeLuminance(backgroundColor) < 0.5
  273. const mod = isLightOnDark ? 1 : -1
  274. let outputColor = null
  275. if (sourceColor) {
  276. // Color is defined in source color
  277. let targetColor = sourceColor
  278. if (targetColor === 'transparent') {
  279. // We take only layers below current one
  280. const layers = getLayers(
  281. getLayerSlot(key),
  282. key,
  283. getOpacitySlot(key) || key,
  284. colors,
  285. opacity
  286. ).slice(0, -1)
  287. targetColor = {
  288. ...alphaBlendLayers(
  289. convert('#FF00FF').rgb,
  290. layers
  291. ),
  292. a: 0
  293. }
  294. } else if (typeof sourceColor === 'string' && sourceColor.startsWith('--')) {
  295. targetColor = computeDynamicColor(
  296. sourceColor,
  297. variableSlot => colors[variableSlot] || sourceColors[variableSlot],
  298. mod
  299. )
  300. } else if (typeof sourceColor === 'string' && sourceColor.startsWith('#')) {
  301. targetColor = convert(targetColor).rgb
  302. }
  303. outputColor = { ...targetColor }
  304. } else if (value.default) {
  305. // same as above except in object form
  306. outputColor = convert(value.default).rgb
  307. } else {
  308. // calculate color
  309. const defaultColorFunc = (mod, dep) => ({ ...dep })
  310. const colorFunc = value.color || defaultColorFunc
  311. if (value.textColor) {
  312. if (value.textColor === 'bw') {
  313. outputColor = contrastRatio(backgroundColor).rgb
  314. } else {
  315. let color = { ...colors[deps[0]] }
  316. if (value.color) {
  317. color = colorFunc(mod, ...deps.map((dep) => ({ ...colors[dep] })))
  318. }
  319. outputColor = getTextColor(
  320. backgroundColor,
  321. { ...color },
  322. value.textColor === 'preserve'
  323. )
  324. }
  325. } else {
  326. // background color case
  327. outputColor = colorFunc(
  328. mod,
  329. ...deps.map((dep) => ({ ...colors[dep] }))
  330. )
  331. }
  332. }
  333. if (!outputColor) {
  334. throw new Error('Couldn\'t generate color for ' + key)
  335. }
  336. const opacitySlot = value.opacity || getOpacitySlot(key)
  337. const ownOpacitySlot = value.opacity
  338. if (ownOpacitySlot === null) {
  339. outputColor.a = 1
  340. } else if (sourceColor === 'transparent') {
  341. outputColor.a = 0
  342. } else {
  343. const opacityOverriden = ownOpacitySlot && sourceOpacity[opacitySlot] !== undefined
  344. const dependencySlot = deps[0]
  345. const dependencyColor = dependencySlot && colors[dependencySlot]
  346. if (!ownOpacitySlot && dependencyColor && !value.textColor && ownOpacitySlot !== null) {
  347. // Inheriting color from dependency (weird, i know)
  348. // except if it's a text color or opacity slot is set to 'null'
  349. outputColor.a = dependencyColor.a
  350. } else if (!dependencyColor && !opacitySlot) {
  351. // Remove any alpha channel if no dependency and no opacitySlot found
  352. delete outputColor.a
  353. } else {
  354. // Otherwise try to assign opacity
  355. if (dependencyColor && dependencyColor.a === 0) {
  356. // transparent dependency shall make dependents transparent too
  357. outputColor.a = 0
  358. } else {
  359. // Otherwise check if opacity is overriden and use that or default value instead
  360. outputColor.a = Number(
  361. opacityOverriden
  362. ? sourceOpacity[opacitySlot]
  363. : (OPACITIES[opacitySlot] || {}).defaultValue
  364. )
  365. }
  366. }
  367. }
  368. if (Number.isNaN(outputColor.a) || outputColor.a === undefined) {
  369. outputColor.a = 1
  370. }
  371. if (opacitySlot) {
  372. return {
  373. colors: { ...colors, [key]: outputColor },
  374. opacity: { ...opacity, [opacitySlot]: outputColor.a }
  375. }
  376. } else {
  377. return {
  378. colors: { ...colors, [key]: outputColor },
  379. opacity
  380. }
  381. }
  382. }, { colors: {}, opacity: {} })
  383. export const composePreset = (colors, radii, shadows, fonts) => {
  384. return {
  385. rules: {
  386. ...shadows.rules,
  387. ...colors.rules,
  388. ...radii.rules,
  389. ...fonts.rules
  390. },
  391. theme: {
  392. ...shadows.theme,
  393. ...colors.theme,
  394. ...radii.theme,
  395. ...fonts.theme
  396. }
  397. }
  398. }
  399. export const generatePreset = (input) => {
  400. const colors = generateColors(input)
  401. return composePreset(
  402. colors,
  403. generateRadii(input),
  404. generateShadows(input, colors.theme.colors, colors.mod),
  405. generateFonts(input)
  406. )
  407. }
  408. export const getCssShadow = (input, usesDropShadow) => {
  409. if (input.length === 0) {
  410. return 'none'
  411. }
  412. return input
  413. .filter(_ => usesDropShadow ? _.inset : _)
  414. .map((shad) => [
  415. shad.x,
  416. shad.y,
  417. shad.blur,
  418. shad.spread
  419. ].map(_ => _ + 'px').concat([
  420. getCssColor(shad.color, shad.alpha),
  421. shad.inset ? 'inset' : ''
  422. ]).join(' ')).join(', ')
  423. }
  424. const getCssShadowFilter = (input) => {
  425. if (input.length === 0) {
  426. return 'none'
  427. }
  428. return input
  429. // drop-shadow doesn't support inset or spread
  430. .filter((shad) => !shad.inset && Number(shad.spread) === 0)
  431. .map((shad) => [
  432. shad.x,
  433. shad.y,
  434. // drop-shadow's blur is twice as strong compared to box-shadow
  435. shad.blur / 2
  436. ].map(_ => _ + 'px').concat([
  437. getCssColor(shad.color, shad.alpha)
  438. ]).join(' '))
  439. .map(_ => `drop-shadow(${_})`)
  440. .join(' ')
  441. }
  442. export const generateColors = (themeData) => {
  443. const sourceColors = !themeData.themeEngineVersion
  444. ? colors2to3(themeData.colors || themeData)
  445. : themeData.colors || themeData
  446. const { colors, opacity } = getColors(sourceColors, themeData.opacity || {})
  447. const htmlColors = Object.entries(colors)
  448. .reduce((acc, [k, v]) => {
  449. if (!v) return acc
  450. acc.solid[k] = rgb2hex(v)
  451. acc.complete[k] = typeof v.a === 'undefined' ? rgb2hex(v) : rgba2css(v)
  452. return acc
  453. }, { complete: {}, solid: {} })
  454. return {
  455. rules: {
  456. colors: Object.entries(htmlColors.complete)
  457. .filter(([k, v]) => v)
  458. .map(([k, v]) => `--${k}: ${v}`)
  459. .join(';')
  460. },
  461. theme: {
  462. colors: htmlColors.solid,
  463. opacity
  464. }
  465. }
  466. }
  467. export const generateRadii = (input) => {
  468. let inputRadii = input.radii || {}
  469. // v1 -> v2
  470. if (typeof input.btnRadius !== 'undefined') {
  471. inputRadii = Object
  472. .entries(input)
  473. .filter(([k, v]) => k.endsWith('Radius'))
  474. .reduce((acc, e) => { acc[e[0].split('Radius')[0]] = e[1]; return acc }, {})
  475. }
  476. const radii = Object.entries(inputRadii).filter(([k, v]) => v).reduce((acc, [k, v]) => {
  477. acc[k] = v
  478. return acc
  479. }, {
  480. btn: 4,
  481. input: 4,
  482. checkbox: 2,
  483. panel: 10,
  484. avatar: 5,
  485. avatarAlt: 50,
  486. tooltip: 2,
  487. attachment: 5,
  488. chatMessage: inputRadii.panel
  489. })
  490. return {
  491. rules: {
  492. radii: Object.entries(radii).filter(([k, v]) => v).map(([k, v]) => `--${k}Radius: ${v}px`).join(';')
  493. },
  494. theme: {
  495. radii
  496. }
  497. }
  498. }
  499. export const generateFonts = (input) => {
  500. const fonts = Object.entries(input.fonts || {}).filter(([k, v]) => v).reduce((acc, [k, v]) => {
  501. acc[k] = Object.entries(v).filter(([k, v]) => v).reduce((acc, [k, v]) => {
  502. acc[k] = v
  503. return acc
  504. }, acc[k])
  505. return acc
  506. }, {
  507. interface: {
  508. family: 'sans-serif'
  509. },
  510. input: {
  511. family: 'inherit'
  512. },
  513. post: {
  514. family: 'inherit'
  515. },
  516. postCode: {
  517. family: 'monospace'
  518. }
  519. })
  520. return {
  521. rules: {
  522. fonts: Object
  523. .entries(fonts)
  524. .filter(([k, v]) => v)
  525. .map(([k, v]) => `--${k}Font: ${v.family}`).join(';')
  526. },
  527. theme: {
  528. fonts
  529. }
  530. }
  531. }
  532. const border = (top, shadow) => ({
  533. x: 0,
  534. y: top ? 1 : -1,
  535. blur: 0,
  536. spread: 0,
  537. color: shadow ? '#000000' : '#FFFFFF',
  538. alpha: 0.2,
  539. inset: true
  540. })
  541. const buttonInsetFakeBorders = [border(true, false), border(false, true)]
  542. const inputInsetFakeBorders = [border(true, true), border(false, false)]
  543. const hoverGlow = {
  544. x: 0,
  545. y: 0,
  546. blur: 4,
  547. spread: 0,
  548. color: '--faint',
  549. alpha: 1
  550. }
  551. export const DEFAULT_SHADOWS = {
  552. panel: [{
  553. x: 1,
  554. y: 1,
  555. blur: 4,
  556. spread: 0,
  557. color: '#000000',
  558. alpha: 0.6
  559. }],
  560. topBar: [{
  561. x: 0,
  562. y: 0,
  563. blur: 4,
  564. spread: 0,
  565. color: '#000000',
  566. alpha: 0.6
  567. }],
  568. popup: [{
  569. x: 2,
  570. y: 2,
  571. blur: 3,
  572. spread: 0,
  573. color: '#000000',
  574. alpha: 0.5
  575. }],
  576. avatar: [{
  577. x: 0,
  578. y: 1,
  579. blur: 8,
  580. spread: 0,
  581. color: '#000000',
  582. alpha: 0.7
  583. }],
  584. avatarStatus: [],
  585. panelHeader: [],
  586. button: [{
  587. x: 0,
  588. y: 0,
  589. blur: 2,
  590. spread: 0,
  591. color: '#000000',
  592. alpha: 1
  593. }, ...buttonInsetFakeBorders],
  594. buttonHover: [hoverGlow, ...buttonInsetFakeBorders],
  595. buttonPressed: [hoverGlow, ...inputInsetFakeBorders],
  596. input: [...inputInsetFakeBorders, {
  597. x: 0,
  598. y: 0,
  599. blur: 2,
  600. inset: true,
  601. spread: 0,
  602. color: '#000000',
  603. alpha: 1
  604. }]
  605. }
  606. export const generateShadows = (input, colors) => {
  607. // TODO this is a small hack for `mod` to work with shadows
  608. // this is used to get the "context" of shadow, i.e. for `mod` properly depend on background color of element
  609. const hackContextDict = {
  610. button: 'btn',
  611. panel: 'bg',
  612. top: 'topBar',
  613. popup: 'popover',
  614. avatar: 'bg',
  615. panelHeader: 'panel',
  616. input: 'input'
  617. }
  618. const cleanInputShadows = Object.fromEntries(
  619. Object.entries(input.shadows || {})
  620. .map(([name, shadowSlot]) => [
  621. name,
  622. // defaulting color to black to avoid potential problems
  623. shadowSlot.map(shadowDef => ({ color: '#000000', ...shadowDef }))
  624. ])
  625. )
  626. const inputShadows = cleanInputShadows && !input.themeEngineVersion
  627. ? shadows2to3(cleanInputShadows, input.opacity)
  628. : cleanInputShadows || {}
  629. const shadows = Object.entries({
  630. ...DEFAULT_SHADOWS,
  631. ...inputShadows
  632. }).reduce((shadowsAcc, [slotName, shadowDefs]) => {
  633. const slotFirstWord = slotName.replace(/[A-Z].*$/, '')
  634. const colorSlotName = hackContextDict[slotFirstWord]
  635. const isLightOnDark = relativeLuminance(convert(colors[colorSlotName]).rgb) < 0.5
  636. const mod = isLightOnDark ? 1 : -1
  637. const newShadow = shadowDefs.reduce((shadowAcc, def) => [
  638. ...shadowAcc,
  639. {
  640. ...def,
  641. color: rgb2hex(computeDynamicColor(
  642. def.color,
  643. (variableSlot) => convert(colors[variableSlot]).rgb,
  644. mod
  645. ))
  646. }
  647. ], [])
  648. return { ...shadowsAcc, [slotName]: newShadow }
  649. }, {})
  650. return {
  651. rules: {
  652. shadows: Object
  653. .entries(shadows)
  654. // TODO for v2.2: if shadow doesn't have non-inset shadows with spread > 0 - optionally
  655. // convert all non-inset shadows into filter: drop-shadow() to boost performance
  656. .map(([k, v]) => [
  657. `--${k}Shadow: ${getCssShadow(v)}`,
  658. `--${k}ShadowFilter: ${getCssShadowFilter(v)}`,
  659. `--${k}ShadowInset: ${getCssShadow(v, true)}`
  660. ].join(';'))
  661. .join(';')
  662. },
  663. theme: {
  664. shadows
  665. }
  666. }
  667. }
  668. /**
  669. * This handles compatibility issues when importing v2 theme's shadows to current format
  670. *
  671. * Back in v2 shadows allowed you to use dynamic colors however those used pure CSS3 variables
  672. */
  673. export const shadows2to3 = (shadows, opacity) => {
  674. return Object.entries(shadows).reduce((shadowsAcc, [slotName, shadowDefs]) => {
  675. const isDynamic = ({ color = '#000000' }) => color.startsWith('--')
  676. const getOpacity = ({ color }) => opacity[getOpacitySlot(color.substring(2).split(',')[0])]
  677. const newShadow = shadowDefs.reduce((shadowAcc, def) => [
  678. ...shadowAcc,
  679. {
  680. ...def,
  681. alpha: isDynamic(def) ? getOpacity(def) || 1 : def.alpha
  682. }
  683. ], [])
  684. return { ...shadowsAcc, [slotName]: newShadow }
  685. }, {})
  686. }
  687. export const colors2to3 = (colors) => {
  688. return Object.entries(colors).reduce((acc, [slotName, color]) => {
  689. const btnPositions = ['', 'Panel', 'TopBar']
  690. switch (slotName) {
  691. case 'lightBg':
  692. return { ...acc, highlight: color }
  693. case 'btnText':
  694. return {
  695. ...acc,
  696. ...btnPositions
  697. .reduce(
  698. (statePositionAcc, position) =>
  699. ({ ...statePositionAcc, ['btn' + position + 'Text']: color })
  700. , {}
  701. )
  702. }
  703. default:
  704. return { ...acc, [slotName]: color }
  705. }
  706. }, {})
  707. }