update, should inherit stuff properly now.

merge-requests/1931/head
Henry Jameson 8 months ago
parent d4795d2e3c
commit c34590c439

@ -371,8 +371,7 @@ nav {
border-radius: $fallback--btnRadius;
border-radius: var(--btnRadius, $fallback--btnRadius);
cursor: pointer;
box-shadow: $fallback--buttonShadow;
box-shadow: var(--buttonShadow);
box-shadow: var(--shadow);
font-size: 1em;
font-family: sans-serif;
font-family: var(--interfaceFont, sans-serif);
@ -383,25 +382,14 @@ nav {
i[class*="icon-"],
.svg-inline--fa {
color: $fallback--text;
color: var(--btnText, $fallback--text);
color: var(--icon);
}
&::-moz-focus-inner {
border: none;
}
&:hover {
box-shadow: 0 0 4px rgb(255 255 255 / 30%);
box-shadow: var(--buttonHoverShadow);
}
&:active {
box-shadow:
0 0 4px 0 rgb(255 255 255 / 30%),
0 1px 0 0 rgb(0 0 0 / 20%) inset,
0 -1px 0 0 rgb(255 255 255 / 20%) inset;
box-shadow: var(--buttonPressedShadow);
color: $fallback--text;
color: var(--btnPressedText, $fallback--text);
background-color: $fallback--fg;
@ -487,7 +475,12 @@ nav {
}
input,
textarea,
textarea {
border: none;
display: inline-block;
outline: none;
}
.input {
&.unstyled {
border-radius: 0;
@ -501,15 +494,7 @@ textarea,
border: none;
border-radius: $fallback--inputRadius;
border-radius: var(--inputRadius, $fallback--inputRadius);
box-shadow:
0 1px 0 0 rgb(0 0 0 / 20%) inset,
0 -1px 0 0 rgb(255 255 255 / 20%) inset,
0 0 2px 0 rgb(0 0 0 / 100%) inset;
box-shadow: var(--inputShadow);
background-color: $fallback--fg;
background-color: var(--input, $fallback--fg);
color: $fallback--lightText;
color: var(--inputText, $fallback--lightText);
box-shadow: var(--shadow);
font-family: sans-serif;
font-family: var(--inputFont, sans-serif);
font-size: 1em;
@ -561,11 +546,9 @@ textarea,
width: 1.1em;
height: 1.1em;
border-radius: 100%; // Radio buttons should always be circle
box-shadow: 0 0 2px black inset;
box-shadow: var(--inputShadow);
background-color: var(--background);
box-shadow: var(--shadow);
margin-right: 0.5em;
background-color: $fallback--fg;
background-color: var(--input, $fallback--fg);
vertical-align: top;
text-align: center;
line-height: 1.1;
@ -578,8 +561,9 @@ textarea,
&[type="checkbox"] {
&:checked + label::before {
color: $fallback--text;
color: var(--inputText, $fallback--text);
color: var(--text);
background-color: var(--background);
box-shadow: var(--shadow);
}
&:disabled {

@ -1,11 +1,34 @@
const border = (top, shadow) => ({
x: 0,
y: top ? 1 : -1,
blur: 0,
spread: 0,
color: shadow ? '#000000' : '#FFFFFF',
alpha: 0.2,
inset: true
})
const buttonInsetFakeBorders = [border(true, false), border(false, true)]
const inputInsetFakeBorders = [border(true, true), border(false, false)]
const hoverGlow = {
x: 0,
y: 0,
blur: 4,
spread: 0,
color: '--text',
alpha: 1
}
export default {
name: 'Button',
selector: '.btn',
selector: '.button-default',
states: {
disabled: ':disabled',
toggled: '.toggled',
pressed: ':active',
hover: ':hover'
hover: ':hover',
focused: ':focus-within'
},
variants: {
danger: '.danger',
@ -20,14 +43,49 @@ export default {
{
component: 'Button',
directives: {
background: '--fg'
background: '--fg',
shadow: [{
x: 0,
y: 0,
blur: 2,
spread: 0,
color: '#000000',
alpha: 1
}, ...buttonInsetFakeBorders]
}
},
{
component: 'Button',
state: ['hover'],
directives: {
background: '#FFFFFF'
shadow: [hoverGlow, ...buttonInsetFakeBorders]
}
},
{
component: 'Button',
state: ['hover', 'pressed'],
directives: {
background: '--accent,-24.2',
shadow: [hoverGlow, ...inputInsetFakeBorders]
}
},
{
component: 'Button',
state: ['disabled'],
directives: {
background: '$blend(--background, 0.25, --parent)',
shadow: [...buttonInsetFakeBorders]
}
},
{
component: 'Text',
parent: {
component: 'Button',
state: ['disabled']
},
directives: {
textOpacity: 0.25,
textOpacityMode: 'blend'
}
}
]

@ -0,0 +1,19 @@
export default {
name: 'DropdownMenu',
selector: '.dropdown',
validInnerComponents: [
'Text',
'Icon',
'Input'
],
states: {
hover: ':hover'
},
defaultRules: [
{
directives: {
background: '--fg'
}
}
]
}

@ -0,0 +1,60 @@
const border = (top, shadow) => ({
x: 0,
y: top ? 1 : -1,
blur: 0,
spread: 0,
color: shadow ? '#000000' : '#FFFFFF',
alpha: 0.2,
inset: true
})
const inputInsetFakeBorders = [border(true, true), border(false, false)]
const hoverGlow = {
x: 0,
y: 0,
blur: 4,
spread: 0,
color: '--text',
alpha: 1
}
export default {
name: 'Input',
selector: '.input',
states: {
disabled: ':disabled',
pressed: ':active',
hover: ':hover',
focused: ':focus-within'
},
variants: {
danger: '.danger',
unstyled: '.unstyled',
sublime: '.sublime'
},
validInnerComponents: [
'Text'
],
defaultRules: [
{
directives: {
background: '--fg',
shadow: [{
x: 0,
y: 0,
blur: 2,
spread: 0,
color: '#000000',
alpha: 1
}, ...inputInsetFakeBorders]
}
},
{
state: ['hover'],
directives: {
shadow: [hoverGlow, ...inputInsetFakeBorders]
}
}
]
}

@ -6,13 +6,14 @@ export default {
'Link',
'Icon',
'Button',
'PanelHeader'
'Input',
'PanelHeader',
'DropdownMenu'
],
defaultRules: [
{
component: 'Panel',
directives: {
background: '--fg'
background: '--bg'
}
}
]

@ -12,7 +12,6 @@ export default {
component: 'PanelHeader',
directives: {
background: '--fg'
// opacity: 0.9
}
}
]

@ -0,0 +1,20 @@
export default {
name: 'Popover',
selector: '.popover',
validInnerComponents: [
'Text',
'Link',
'Icon',
'Button',
'Input',
'PanelHeader',
'DropdownMenu'
],
defaultRules: [
{
directives: {
background: '--fg'
}
}
]
}

@ -0,0 +1,17 @@
export default {
name: 'Root',
selector: ':root',
validInnerComponents: [
'Underlay',
'TopBar',
'Popover'
],
defaultRules: [
{
directives: {
background: '--bg',
opacity: 0
}
}
]
}

@ -0,0 +1,18 @@
export default {
name: 'TopBar',
selector: 'nav',
validInnerComponents: [
'Link',
'Text',
'Icon',
'Button',
'Input'
],
defaultRules: [
{
directives: {
background: '--fg'
}
}
]
}

@ -1,6 +1,6 @@
export default {
name: 'Underlay',
selector: '#content',
selector: 'body', // Should be '#content' but for now this is better for testing until I have proper popovers and such...
outOfTreeSelector: '.underlay',
validInnerComponents: [
'Panel'

@ -6,6 +6,10 @@
background-color: $fallback--bg;
background-color: var(--bg, $fallback--bg);
.panel-heading {
background-color: inherit;
}
&::after,
& {
border-radius: $fallback--panelRadius;
@ -131,12 +135,9 @@
align-items: start;
// panel theme
color: var(--panelText);
background-color: $fallback--bg;
background-color: var(--bg, $fallback--bg);
&::after {
background-color: $fallback--fg;
background-color: var(--panel, $fallback--fg);
background-color: var(--background);
z-index: -2;
border-radius: $fallback--panelRadius $fallback--panelRadius 0 0;
border-radius: var(--panelRadius, $fallback--panelRadius) var(--panelRadius, $fallback--panelRadius) 0 0;

@ -20,8 +20,8 @@ export const applyTheme = (input) => {
styleSheet.toString()
styleSheet.insertRule(`:root { ${rules.radii} }`, 'index-max')
styleSheet.insertRule(`:root { ${rules.colors} }`, 'index-max')
styleSheet.insertRule(`:root { ${rules.shadows} }`, 'index-max')
// styleSheet.insertRule(`:root { ${rules.colors} }`, 'index-max')
// styleSheet.insertRule(`:root { ${rules.shadows} }`, 'index-max')
styleSheet.insertRule(`:root { ${rules.fonts} }`, 'index-max')
themes3.css.forEach(rule => {
console.log(rule)

@ -0,0 +1,176 @@
export default [
'bg',
'wallpaper',
'fg',
'text',
'underlay',
'link',
'accent',
'faint',
'faintLink',
'postFaintLink',
'cBlue',
'cRed',
'cGreen',
'cOrange',
'profileBg',
'profileTint',
'highlight',
'highlightLightText',
'highlightPostLink',
'highlightFaintText',
'highlightFaintLink',
'highlightPostFaintLink',
'highlightText',
'highlightLink',
'highlightIcon',
'popover',
'popoverLightText',
'popoverPostLink',
'popoverFaintText',
'popoverFaintLink',
'popoverPostFaintLink',
'popoverText',
'popoverLink',
'popoverIcon',
'selectedPost',
'selectedPostFaintText',
'selectedPostLightText',
'selectedPostPostLink',
'selectedPostFaintLink',
'selectedPostText',
'selectedPostLink',
'selectedPostIcon',
'selectedMenu',
'selectedMenuLightText',
'selectedMenuFaintText',
'selectedMenuFaintLink',
'selectedMenuText',
'selectedMenuLink',
'selectedMenuIcon',
'selectedMenuPopover',
'selectedMenuPopoverLightText',
'selectedMenuPopoverFaintText',
'selectedMenuPopoverFaintLink',
'selectedMenuPopoverText',
'selectedMenuPopoverLink',
'selectedMenuPopoverIcon',
'lightText',
'postLink',
'postGreentext',
'postCyantext',
'border',
'poll',
'pollText',
'icon',
// Foreground,
'fgText',
'fgLink',
// Panel header,
'panel',
'panelText',
'panelFaint',
'panelLink',
// Top bar,
'topBar',
'topBarLink',
// Tabs,
'tab',
'tabText',
'tabActiveText',
// Buttons,
'btn',
'btnText',
'btnPanelText',
'btnTopBarText',
// Buttons: pressed,
'btnPressed',
'btnPressedText',
'btnPressedPanel',
'btnPressedPanelText',
'btnPressedTopBar',
'btnPressedTopBarText',
// Buttons: toggled,
'btnToggled',
'btnToggledText',
'btnToggledPanelText',
'btnToggledTopBarText',
// Buttons: disabled,
'btnDisabled',
'btnDisabledText',
'btnDisabledPanelText',
'btnDisabledTopBarText',
// Input fields,
'input',
'inputText',
'inputPanelText',
'inputTopbarText',
'alertError',
'alertErrorText',
'alertErrorPanelText',
'alertWarning',
'alertWarningText',
'alertWarningPanelText',
'alertSuccess',
'alertSuccessText',
'alertSuccessPanelText',
'alertNeutral',
'alertNeutralText',
'alertNeutralPanelText',
'alertPopupError',
'alertPopupErrorText',
'alertPopupWarning',
'alertPopupWarningText',
'alertPopupSuccess',
'alertPopupSuccessText',
'alertPopupNeutral',
'alertPopupNeutralText',
'badgeNotification',
'badgeNotificationText',
'badgeNeutral',
'badgeNeutralText',
'chatBg',
'chatMessageIncomingBg',
'chatMessageIncomingText',
'chatMessageIncomingLink',
'chatMessageIncomingBorder',
'chatMessageOutgoingBg',
'chatMessageOutgoingText',
'chatMessageOutgoingLink',
'chatMessageOutgoingBorder'
]

@ -0,0 +1,58 @@
import allKeys from './theme2_keys'
// keys that are meant to be used globally, i.e. what's the rest of the theme is based upon.
const basePaletteKeys = new Set([
'bg',
'fg',
'text',
'link',
'accent',
'cBlue',
'cRed',
'cGreen',
'cOrange'
])
// Keys that are not available in editor and never meant to be edited
const hiddenKeys = new Set([
'profileBg',
'profileTint'
])
const extendedBasePrefixes = [
'border',
'icon',
'highlight',
'lightText',
'popover',
'panel',
'topBar',
'tab',
'btn',
'input',
'selectedMenu',
'alert',
'badge',
'post',
'selectedPost', // wrong nomenclature
'poll',
'chatBg',
'chatMessageIncoming',
'chatMessageOutgoing'
]
const extendedBaseKeys = Object.fromEntries(extendedBasePrefixes.map(prefix => [prefix, allKeys.filter(k => k.startsWith(prefix))]))
// Keysets that are only really used intermideately, i.e. to generate other colors
const temporary = new Set([
'border',
'highlight'
])
const temporaryColors = {}

@ -1,24 +1,89 @@
import { convert, brightness } from 'chromatism'
import merge from 'lodash.merge'
import { alphaBlend, getTextColor, rgba2css, mixrgb, relativeLuminance } from '../color_convert/color_convert.js'
import {
alphaBlend,
getTextColor,
rgba2css,
mixrgb,
relativeLuminance
} from '../color_convert/color_convert.js'
import Root from 'src/components/root.style.js'
import TopBar from 'src/components/top_bar.style.js'
import Underlay from 'src/components/underlay.style.js'
import Popover from 'src/components/popover.style.js'
import DropdownMenu from 'src/components/dropdown_menu.style.js'
import Panel from 'src/components/panel.style.js'
import PanelHeader from 'src/components/panel_header.style.js'
import Button from 'src/components/button.style.js'
import Input from 'src/components/input.style.js'
import Text from 'src/components/text.style.js'
import Link from 'src/components/link.style.js'
import Icon from 'src/components/icon.style.js'
const root = Underlay
export const DEFAULT_SHADOWS = {
panel: [{
x: 1,
y: 1,
blur: 4,
spread: 0,
color: '#000000',
alpha: 0.6
}],
topBar: [{
x: 0,
y: 0,
blur: 4,
spread: 0,
color: '#000000',
alpha: 0.6
}],
popup: [{
x: 2,
y: 2,
blur: 3,
spread: 0,
color: '#000000',
alpha: 0.5
}],
avatar: [{
x: 0,
y: 1,
blur: 8,
spread: 0,
color: '#000000',
alpha: 0.7
}],
avatarStatus: [],
panelHeader: []
}
const components = {
Root,
Text,
Link,
Icon,
Underlay,
Popover,
DropdownMenu,
Panel,
PanelHeader,
TopBar,
Button,
Text,
Link,
Icon
Input
}
// "Unrolls" a tree structure of item: { parent: { ...item2, parent: { ...item3, parent: {...} } }}
// into an array [item2, item3] for iterating
const unroll = (item) => {
const out = []
let currentParent = item.parent
while (currentParent) {
const { parent: newParent, ...rest } = currentParent
out.push(rest)
currentParent = newParent
}
return out
}
// This gives you an array of arrays of all possible unique (i.e. order-insensitive) combinations
@ -38,7 +103,9 @@ export const getAllPossibleCombinations = (array) => {
return combos.reduce((acc, x) => [...acc, ...x], [])
}
export const ruleToSelector = (rule, isParent) => {
// Converts rule, parents and their criteria into a CSS (or path if ignoreOutOfTreeSelector == true) selector
export const ruleToSelector = (rule, ignoreOutOfTreeSelector, isParent) => {
if (!rule && !isParent) return null
const component = components[rule.component]
const { states, variants, selector, outOfTreeSelector } = component
@ -51,10 +118,12 @@ export const ruleToSelector = (rule, isParent) => {
}
let realSelector
if (isParent) {
if (selector === ':root') {
realSelector = ''
} else if (isParent) {
realSelector = selector
} else {
if (outOfTreeSelector) realSelector = outOfTreeSelector
if (outOfTreeSelector && !ignoreOutOfTreeSelector) realSelector = outOfTreeSelector
else realSelector = selector
}
@ -67,123 +136,133 @@ export const ruleToSelector = (rule, isParent) => {
.join('')
if (rule.parent) {
return ruleToSelector(rule.parent, true) + ' ' + selectors
return (ruleToSelector(rule.parent, ignoreOutOfTreeSelector, true) + ' ' + selectors).trim()
}
return selectors
return selectors.trim()
}
export const init = (extraRuleset, palette) => {
const rootName = root.name
const rules = []
const rulesByComponent = {}
const ruleset = [
...Object.values(components).map(c => c.defaultRules || []).reduce((acc, arr) => [...acc, ...arr], []),
...extraRuleset
]
const combinationsMatch = (criteria, subject) => {
if (criteria.component !== subject.component) return false
const addRule = (rule) => {
rules.push(rule)
rulesByComponent[rule.component] = rulesByComponent[rule.component] || []
rulesByComponent[rule.component].push(rule)
// All variants inherit from normal
const subjectVariant = Object.prototype.hasOwnProperty.call(subject, 'variant') ? subject.variant : 'normal'
if (subjectVariant !== 'normal') {
if (criteria.variant !== subject.variant) return false
}
const findRules = (searchCombination, parent) => rule => {
// inexact search
const doesCombinationMatch = () => {
if (searchCombination.component !== rule.component) return false
const ruleVariant = Object.prototype.hasOwnProperty.call(rule, 'variant') ? rule.variant : 'normal'
const subjectStatesSet = new Set(['normal', ...(subject.state || [])])
const criteriaStatesSet = new Set(['normal', ...(criteria.state || [])])
if (ruleVariant !== 'normal') {
if (searchCombination.variant !== rule.variant) return false
}
// Subject states > 1 essentially means state is "normal" and therefore matches
if (subjectStatesSet.size > 1) {
const setsAreEqual =
[...criteriaStatesSet].every(state => subjectStatesSet.has(state)) &&
[...subjectStatesSet].every(state => criteriaStatesSet.has(state))
const ruleHasStateDefined = Object.prototype.hasOwnProperty.call(rule, 'state')
let ruleStateSet
if (ruleHasStateDefined) {
ruleStateSet = new Set(['normal', ...rule.state])
} else {
ruleStateSet = new Set(['normal'])
if (!setsAreEqual) return false
}
if (ruleStateSet.size > 1) {
const ruleStatesSet = ruleStateSet
const combinationSet = new Set(['normal', ...searchCombination.state])
const setsAreEqual = searchCombination.state.every(state => ruleStatesSet.has(state)) &&
[...ruleStatesSet].every(state => combinationSet.has(state))
return setsAreEqual
} else {
return true
}
}
}
const combinationMatches = doesCombinationMatch()
if (!parent || !combinationMatches) return combinationMatches
const findRules = criteria => subject => {
// If we searching for "general" rules - ignore "specific" ones
if (criteria.parent === null && !!subject.parent) return false
if (!combinationsMatch(criteria, subject)) return false
// exact search
if (criteria.parent !== undefined && criteria.parent !== null) {
if (!subject.parent) return true
const pathCriteria = unroll(criteria)
const pathSubject = unroll(subject)
if (pathCriteria.length < pathSubject.length) return false
// unroll parents into array
const unroll = (item) => {
const out = []
let currentParent = item.parent
while (currentParent) {
const { parent: newParent, ...rest } = currentParent
out.push(rest)
currentParent = newParent
// Search: .a .b .c
// Matches: .a .b .c; .b .c; .c; .z .a .b .c
// Does not match .a .b .c .d, .a .b .e
for (let i = 0; i < pathCriteria.length; i++) {
const criteriaParent = pathCriteria[i]
const subjectParent = pathSubject[i]
if (!subjectParent) return true
if (!combinationsMatch(criteriaParent, subjectParent)) return false
}
return out
}
const { parent: _, ...rest } = parent
const pathSearch = [rest, ...unroll(parent)]
const pathRule = unroll(rule)
if (pathSearch.length !== pathRule.length) return false
const pathsMatch = pathSearch.every((searchRule, i) => {
const existingRule = pathRule[i]
if (existingRule.component !== searchRule.component) return false
if (existingRule.variant !== searchRule.variant) return false
const existingRuleStatesSet = new Set(['normal', ...(existingRule.state || [])])
const searchStatesSet = new Set(['normal', ...(searchRule.state || [])])
const setsAreEqual = existingRule.state.every(state => searchStatesSet.has(state)) &&
[...searchStatesSet].every(state => existingRuleStatesSet.has(state))
return setsAreEqual
})
return pathsMatch
}
return true
}
const findLowerLevelRule = (parent, filter = () => true) => {
let lowerLevelComponent = null
let currentParent = parent
while (currentParent) {
const rulesParent = ruleset.filter(findRules(currentParent))
rulesParent > 1 && console.warn('OOPS')
lowerLevelComponent = rulesParent[rulesParent.length - 1]
currentParent = currentParent.parent
if (lowerLevelComponent && filter(lowerLevelComponent)) currentParent = null
}
return filter(lowerLevelComponent) ? lowerLevelComponent : null
export const init = (extraRuleset, palette) => {
const cache = {}
const computed = {}
const rules = []
const ruleset = [
...Object.values(components).map(c => c.defaultRules.map(r => ({ component: c.name, ...r })) || []).reduce((acc, arr) => [...acc, ...arr], []),
...extraRuleset
]
const virtualComponents = new Set(Object.values(components).filter(c => c.virtual).map(c => c.name))
const addRule = (rule) => {
rules.push(rule)
}
const findColor = (color, background) => {
if (typeof color !== 'string' || !color.startsWith('--')) return color
const findColor = (color, inheritedBackground, lowerLevelBackground) => {
if (typeof color !== 'string' || (!color.startsWith('--') && !color.startsWith('$'))) return color
let targetColor = null
// Color references other color
if (color.startsWith('--')) {
const [variable, modifier] = color.split(/,/g).map(str => str.trim())
const variableSlot = variable.substring(2)
if (variableSlot.startsWith('parent')) {
// TODO support more than just background?
if (variableSlot === 'parent') {
targetColor = lowerLevelBackground
}
} else {
switch (variableSlot) {
case 'background':
targetColor = inheritedBackground
break
default:
targetColor = palette[variableSlot]
}
}
if (modifier) {
const effectiveBackground = background ?? targetColor
const effectiveBackground = lowerLevelBackground ?? targetColor
const isLightOnDark = relativeLuminance(convert(effectiveBackground).rgb) < 0.5
const mod = isLightOnDark ? 1 : -1
targetColor = brightness(Number.parseFloat(modifier) * mod, targetColor).rgb
}
}
if (color.startsWith('$')) {
try {
const { funcName, argsString } = /\$(?<funcName>\w+)\((?<argsString>[a-zA-Z0-9-,.'"\s]*)\)/.exec(color).groups
const args = argsString.split(/,/g).map(a => a.trim())
switch (funcName) {
case 'blend': {
if (args.length !== 3) {
throw new Error(`$blend requires 3 arguments, ${args.length} were provided`)
}
const backgroundArg = findColor(args[2], inheritedBackground, lowerLevelBackground)
const foregroundArg = findColor(args[0], inheritedBackground, lowerLevelBackground)
const amount = Number(args[1])
targetColor = alphaBlend(backgroundArg, amount, foregroundArg)
break
}
}
} catch (e) {
console.error('Failure executing color function', e)
targetColor = '#FF00FF'
}
}
// Color references other color
return targetColor
}
const getTextColorAlpha = (rule, lowerRule, value) => {
const cssColorString = (color, alpha) => rgba2css({ ...convert(color).rgb, a: alpha })
const getTextColorAlpha = (rule, lowerColor, value) => {
const opacity = rule.directives.textOpacity
const backgroundColor = convert(lowerRule.cache.background).rgb
const backgroundColor = convert(lowerColor).rgb
const textColor = convert(findColor(value, backgroundColor)).rgb
if (opacity === null || opacity === undefined || opacity >= 1) {
return convert(textColor).hex
@ -202,6 +281,44 @@ export const init = (extraRuleset, palette) => {
}
}
const getCssShadow = (input, usesDropShadow) => {
if (input.length === 0) {
return 'none'
}
return input
.filter(_ => usesDropShadow ? _.inset : _)
.map((shad) => [
shad.x,
shad.y,
shad.blur,
shad.spread
].map(_ => _ + 'px ').concat([
cssColorString(findColor(shad.color), shad.alpha),
shad.inset ? 'inset' : ''
]).join(' ')).join(', ')
}
const getCssShadowFilter = (input) => {
if (input.length === 0) {
return 'none'
}
return input
// drop-shadow doesn't support inset or spread
.filter((shad) => !shad.inset && Number(shad.spread) === 0)
.map((shad) => [
shad.x,
shad.y,
// drop-shadow's blur is twice as strong compared to box-shadow
shad.blur / 2
].map(_ => _ + 'px').concat([
cssColorString(findColor(shad.color), shad.alpha)
]).join(' '))
.map(_ => `drop-shadow(${_})`)
.join(' ')
}
const processInnerComponent = (component, parent) => {
const {
validInnerComponents = [],
@ -210,6 +327,7 @@ export const init = (extraRuleset, palette) => {
name
} = component
// Normalizing states and variants to always include "normal"
const states = { normal: '', ...originalStates }
const variants = { normal: '', ...originalVariants }
const innerComponents = validInnerComponents.map(name => components[name])
@ -219,13 +337,24 @@ export const init = (extraRuleset, palette) => {
return stateCombinations.map(state => ({ variant, state }))
}).reduce((acc, x) => [...acc, ...x], [])
const VIRTUAL_COMPONENTS = new Set(['Text', 'Link', 'Icon'])
stateVariantCombination.forEach(combination => {
let needRuleAdd = false
const soloSelector = ruleToSelector({ component: component.name, ...combination }, true)
const selector = ruleToSelector({ component: component.name, ...combination, parent }, true)
// Inheriting all of the applicable rules
const existingRules = ruleset.filter(findRules({ component: component.name, ...combination, parent }))
const { directives: computedDirectives } = existingRules.reduce((acc, rule) => merge(acc, rule), {})
const computedRule = {
component: component.name,
...combination,
parent,
directives: computedDirectives
}
computed[selector] = computed[selector] || {}
computed[selector].computedRule = computedRule
if (VIRTUAL_COMPONENTS.has(component.name)) {
const selector = component.name + ruleToSelector({ component: component.name, ...combination })
if (virtualComponents.has(component.name)) {
const virtualName = [
'--',
component.name.toLowerCase(),
@ -235,52 +364,46 @@ export const init = (extraRuleset, palette) => {
...combination.state.filter(x => x !== 'normal').toSorted().map(state => state[0].toUpperCase() + state.slice(1).toLowerCase())
].join('')
const lowerLevel = findLowerLevelRule(parent, (r) => {
if (!r) return false
if (components[r.component].validInnerComponents.indexOf(component.name) < 0) return false
if (r.cache.background === undefined) return false
if (r.cache.textDefined) {
return !r.cache.textDefined[selector]
}
return true
})
if (!lowerLevel) return
let inheritedTextColor = computedDirectives.textColor
let inheritedTextOpacity = computedDirectives.textOpacity
let inheritedTextOpacityMode = computedDirectives.textOpacityMode
const lowerLevelTextSelector = [...selector.split(/ /g).slice(0, -1), soloSelector].join(' ')
const lowerLevelTextRule = computed[lowerLevelTextSelector]
let inheritedTextColorRule
const inheritedTextColorRules = findLowerLevelRule(parent, (r) => {
return r.cache?.textDefined?.[selector]
})
if (!inheritedTextColorRule) {
const generalTextColorRules = ruleset.filter(findRules({ component: component.name, ...combination }, null, true))
inheritedTextColorRule = generalTextColorRules.reduce((acc, rule) => merge(acc, rule), {})
} else {
inheritedTextColorRule = inheritedTextColorRules.reduce((acc, rule) => merge(acc, rule), {})
if (inheritedTextColor == null || inheritedTextOpacity == null || inheritedTextOpacityMode == null) {
inheritedTextColor = computedDirectives.textColor ?? lowerLevelTextRule.textColor
inheritedTextOpacity = computedDirectives.textOpacity ?? lowerLevelTextRule.textOpacity
inheritedTextOpacityMode = computedDirectives.textOpacityMode ?? lowerLevelTextRule.textOpacityMode
}
let inheritedTextColor
let inheritedTextOpacity = {}
if (inheritedTextColorRule) {
inheritedTextColor = findColor(inheritedTextColorRule.directives.textColor, convert(lowerLevel.cache.background).rgb)
// also inherit opacity settings
const { textOpacity, textOpacityMode } = inheritedTextColorRule.directives
inheritedTextOpacity = { textOpacity, textOpacityMode }
} else {
// Emergency fallback
inheritedTextColor = '#000000'
const newTextRule = {
...computedRule,
directives: {
...computedRule.directives,
textColor: inheritedTextColor,
textOpacity: inheritedTextOpacity,
textOpacityMode: inheritedTextOpacityMode
}
}
const lowerLevelSelector = selector.split(/ /g).slice(0, -1).join(' ')
const lowerLevelBackground = cache[lowerLevelSelector].background
const textColor = getTextColor(
convert(lowerLevel.cache.background).rgb,
convert(inheritedTextColor).rgb,
component.name === 'Link' // make it configurable?
convert(lowerLevelBackground).rgb,
// TODO properly provide "parent" text color?
convert(findColor(inheritedTextColor, null, lowerLevelBackground)).rgb,
true // component.name === 'Link' || combination.variant === 'greentext' // make it configurable?
)
lowerLevel.cache.textDefined = lowerLevel.cache.textDefined || {}
lowerLevel.cache.textDefined[selector] = textColor
lowerLevel.virtualDirectives = lowerLevel.virtualDirectives || {}
lowerLevel.virtualDirectives[virtualName] = getTextColorAlpha(inheritedTextColorRule, lowerLevel, textColor)
// Storing color data in lower layer to use as custom css properties
cache[lowerLevelSelector].textDefined = cache[lowerLevelSelector].textDefined || {}
cache[lowerLevelSelector].textDefined[selector] = textColor
const virtualDirectives = {}
virtualDirectives[virtualName] = getTextColorAlpha(newTextRule, lowerLevelBackground, textColor)
// lastRule.computed = lastRule.computed || {}
const directives = {
textColor,
@ -288,45 +411,54 @@ export const init = (extraRuleset, palette) => {
}
// Debug: lets you see what it think background color should be
directives.background = convert(lowerLevel.cache.background).hex
// directives.background = convert(cache[lowerLevelSelector].background).hex
addRule({
parent,
virtual: true,
component: component.name,
...combination,
cache: { background: lowerLevel.cache.background },
directives
directives,
virtualDirectives
})
} else {
const existingGlobalRules = ruleset.filter(findRules({ component: component.name, ...combination }, null))
const existingRules = ruleset.filter(findRules({ component: component.name, ...combination }, parent))
// Global (general) rules
if (existingGlobalRules.length !== 0) {
const totalRule = existingGlobalRules.reduce((acc, rule) => merge(acc, rule), {})
const { directives } = totalRule
// last rule is used as a cache
const lastRule = existingGlobalRules[existingGlobalRules.length - 1]
lastRule.cache = lastRule.cache || {}
cache[selector] = cache[selector] || {}
computed[selector] = computed[selector] || {}
if (computedDirectives.background) {
let inheritRule = null
const variantRules = ruleset.filter(findRules({ component: component.name, variant: combination.variant, parent }))
const lastVariantRule = variantRules[variantRules.length - 1]
if (lastVariantRule) {
inheritRule = lastVariantRule
} else {
const normalRules = ruleset.filter(findRules({ component: component.name, parent }))
const lastNormalRule = normalRules[normalRules.length - 1]
inheritRule = lastNormalRule
}
if (directives.background) {
const rgb = convert(findColor(directives.background)).rgb
const inheritSelector = ruleToSelector({ ...inheritRule, parent }, true)
const inheritedBackground = cache[inheritSelector].background
const lowerLevelSelector = selector.split(/ /g).slice(0, -1).join(' ')
// TODO: DEFAULT TEXT COLOR
const bg = findLowerLevelRule(parent)?.cache.background || convert('#FFFFFF').rgb
const bg = cache[lowerLevelSelector]?.background || convert('#FFFFFF').rgb
if (!lastRule.cache.background) {
const blend = directives.opacity < 1 ? alphaBlend(rgb, directives.opacity, bg) : rgb
lastRule.cache.background = blend
console.log('SELECTOR', lowerLevelSelector)
needRuleAdd = true
}
}
const rgb = convert(findColor(computedDirectives.background, inheritedBackground, cache[lowerLevelSelector].background)).rgb
if (needRuleAdd) {
addRule(lastRule)
if (!cache[selector].background) {
const blend = computedDirectives.opacity < 1 ? alphaBlend(rgb, computedDirectives.opacity, bg) : rgb
cache[selector].background = blend
computed[selector].background = rgb
addRule({
component: component.name,
...combination,
parent,
directives: computedDirectives
})
}
}
@ -338,12 +470,12 @@ export const init = (extraRuleset, palette) => {
})
}
processInnerComponent(components[rootName])
processInnerComponent(components.Root, { component: 'Root' })
return {
raw: rules,
css: rules.map(rule => {
if (rule.virtual) return ''
// if (rule.virtual) return ''
let selector = ruleToSelector(rule).replace(/\/\*.*\*\//g, '')
if (!selector) {
@ -356,10 +488,23 @@ export const init = (extraRuleset, palette) => {
return ' ' + k + ': ' + v
}).join(';\n')
const directives = Object.entries(rule.directives).map(([k, v]) => {
let directives
if (rule.component !== 'Root') {
directives = Object.entries(rule.directives).map(([k, v]) => {
switch (k) {
case 'shadow': {
return ' ' + [
'--shadow: ' + getCssShadow(v),
'--shadowFilter: ' + getCssShadowFilter(v),
'--shadowInset: ' + getCssShadow(v, true)
].join(';\n ')
}
case 'background': {
return 'background-color: ' + rgba2css({ ...convert(findColor(v)).rgb, a: rule.directives.opacity ?? 1 })
const color = cssColorString(computed[ruleToSelector(rule, true)].background, rule.directives.opacity)
return [
'background-color: ' + color,
' --background: ' + color
].join(';\n')
}
case 'textColor': {
return 'color: ' + v
@ -367,11 +512,14 @@ export const init = (extraRuleset, palette) => {
default: return ''
}
}).filter(x => x).map(x => ' ' + x).join(';\n')
} else {
directives = {}
}
return [
header,
directives + ';',
' color: var(--text);',
!rule.virtual ? ' color: var(--text);' : '',
'',
virtualDirectives,
footer

@ -0,0 +1,9 @@
export default {
name: 'TopBar',
selector: 'nav',
validInnerComponents: [
'Link',
'Text',
'Icon'
]
}
Loading…
Cancel
Save