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()
}
const combinationsMatch = (criteria, subject) => {
if (criteria.component !== subject.component) return false
// 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 subjectStatesSet = new Set(['normal', ...(subject.state || [])])
const criteriaStatesSet = new Set(['normal', ...(criteria.state || [])])
// 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))
if (!setsAreEqual) return false
}
return true
}
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
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
// 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 true
}
export const init = (extraRuleset, palette) => {
const rootName = root.name
const cache = {}
const computed = {}
const rules = []
const rulesByComponent = {}
const ruleset = [
...Object.values(components).map(c => c.defaultRules || []).reduce((acc, arr) => [...acc, ...arr], []),
...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)
rulesByComponent[rule.component] = rulesByComponent[rule.component] || []
rulesByComponent[rule.component].push(rule)
}
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'
if (ruleVariant !== 'normal') {
if (searchCombination.variant !== rule.variant) return false
}
const ruleHasStateDefined = Object.prototype.hasOwnProperty.call(rule, 'state')
let ruleStateSet
if (ruleHasStateDefined) {
ruleStateSet = new Set(['normal', ...rule.state])
const findColor = (color, inheritedBackground, lowerLevelBackground) => {
if (typeof color !== 'string' || (!color.startsWith('--') && !color.startsWith('$'))) return color
let targetColor = null
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 {
ruleStateSet = new Set(['normal'])
switch (variableSlot) {
case 'background':
targetColor = inheritedBackground
break
default:
targetColor = palette[variableSlot]
}
}
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
if (modifier) {
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
}
}
const combinationMatches = doesCombinationMatch()
if (!parent || !combinationMatches) return combinationMatches
// exact search
// 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
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'
}
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
}
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
}
const findColor = (color, background) => {
if (typeof color !== 'string' || !color.startsWith('--')) return color
let targetColor = null
// Color references other color
const [variable, modifier] = color.split(/,/g).map(str => str.trim())
const variableSlot = variable.substring(2)
targetColor = palette[variableSlot]
if (modifier) {
const effectiveBackground = background ?? targetColor
const isLightOnDark = relativeLuminance(convert(effectiveBackground).rgb) < 0.5
const mod = isLightOnDark ? 1 : -1
targetColor = brightness(Number.parseFloat(modifier) * mod, targetColor).rgb
}
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
}
if (VIRTUAL_COMPONENTS.has(component.name)) {
const selector = component.name + ruleToSelector({ component: component.name, ...combination })
computed[selector] = computed[selector] || {}
computed[selector].computedRule = computedRule
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))
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
}
// Global (general) rules
if (existingGlobalRules.length !== 0) {
const totalRule = existingGlobalRules.reduce((acc, rule) => merge(acc, rule), {})
const { directives } = totalRule
const inheritSelector = ruleToSelector({ ...inheritRule, parent }, true)
const inheritedBackground = cache[inheritSelector].background
const lowerLevelSelector = selector.split(/ /g).slice(0, -1).join(' ')
// last rule is used as a cache
const lastRule = existingGlobalRules[existingGlobalRules.length - 1]
lastRule.cache = lastRule.cache || {}
// TODO: DEFAULT TEXT COLOR
const bg = cache[lowerLevelSelector]?.background || convert('#FFFFFF').rgb
if (directives.background) {
const rgb = convert(findColor(directives.background)).rgb
console.log('SELECTOR', lowerLevelSelector)
// TODO: DEFAULT TEXT COLOR
const bg = findLowerLevelRule(parent)?.cache.background || convert('#FFFFFF').rgb
const rgb = convert(findColor(computedDirectives.background, inheritedBackground, cache[lowerLevelSelector].background)).rgb
if (!lastRule.cache.background) {
const blend = directives.opacity < 1 ? alphaBlend(rgb, directives.opacity, bg) : rgb
lastRule.cache.background = blend
if (!cache[selector].background) {
const blend = computedDirectives.opacity < 1 ? alphaBlend(rgb, computedDirectives.opacity, bg) : rgb
cache[selector].background = blend
computed[selector].background = rgb
needRuleAdd = true
}
}
if (needRuleAdd) {
addRule(lastRule)
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,22 +488,38 @@ export const init = (extraRuleset, palette) => {
return ' ' + k + ': ' + v
}).join(';\n')
const directives = Object.entries(rule.directives).map(([k, v]) => {
switch (k) {
case 'background': {
return 'background-color: ' + rgba2css({ ...convert(findColor(v)).rgb, a: rule.directives.opacity ?? 1 })
}
case 'textColor': {
return 'color: ' + 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': {
const color = cssColorString(computed[ruleToSelector(rule, true)].background, rule.directives.opacity)
return [
'background-color: ' + color,
' --background: ' + color
].join(';\n')
}
case 'textColor': {
return 'color: ' + v
}
default: return ''
}
default: return ''
}
}).filter(x => x).map(x => ' ' + x).join(';\n')
}).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