diff --git a/packages/ui/src/components/va-button/VaButton.stories.ts b/packages/ui/src/components/va-button/VaButton.stories.ts index 2149eea298..6c031f086d 100644 --- a/packages/ui/src/components/va-button/VaButton.stories.ts +++ b/packages/ui/src/components/va-button/VaButton.stories.ts @@ -1,5 +1,5 @@ import { defineComponent } from 'vue' -import VaButton from './VaButton.vue' +import { VaButton } from './' import VaButtonDemo from './VaButton.demo.vue' export default { @@ -22,3 +22,12 @@ export const RightShift = () => ({ `, }) + +export const StyleAttributes = () => ({ + components: { VaButton }, + template: ` +
+ Text +
+ `, +}) diff --git a/packages/ui/src/components/va-button/VaButton.vue b/packages/ui/src/components/va-button/VaButton.vue index bcc6a7c5cf..b624e41950 100644 --- a/packages/ui/src/components/va-button/VaButton.vue +++ b/packages/ui/src/components/va-button/VaButton.vue @@ -223,8 +223,8 @@ defineExpose({ } &::before { - background: v-bind(backgroundColor); - opacity: v-bind(backgroundColorOpacity); + background: var(--va-button-background, v-bind(backgroundColor)); + opacity: var(--va-button-background-opacity, v-bind(backgroundColorOpacity)); } &::after { diff --git a/packages/ui/src/composables/useColors.ts b/packages/ui/src/composables/useColors.ts index 8631ff56e4..6b4dbc7984 100644 --- a/packages/ui/src/composables/useColors.ts +++ b/packages/ui/src/composables/useColors.ts @@ -60,20 +60,12 @@ export const useColors = () => { return colors } - /** + /** * Returns color from config variables by name or return prop if color is a valid hex, hsl, hsla, rgb or rgba color. * @param prop - should be color name or color in hex, hsl, hsla, rgb or rgba format. * @param preferVariables - function should return (if possible) CSS variable instead of hex (hex is needed to set opacity). - * @param defaultColor - this color will be used if prop is invalid. */ - const getColor = (prop?: string, defaultColor?: string, preferVariables?: boolean): CssColor => { - if (!defaultColor) { - /** - * Most default color - fallback when nothing else is found. - */ - defaultColor = colors.primary - } - + const getColorStrict = (prop: string, preferVariables?: boolean): CssColor | null => { if (prop === 'transparent') { return '#ffffff00' } @@ -90,10 +82,6 @@ export const useColors = () => { } } - if (!prop) { - prop = getColor(defaultColor) - } - const colorValue = colors[prop] || colors[normalizeColorName(prop)] if (colorValue) { return preferVariables ? `var(${cssVariableName(prop)})` : colorValue @@ -110,7 +98,23 @@ export const useColors = () => { warn(`'${prop}' is not a proper color! Use HEX or default color themes names (https://vuestic.dev/en/styles/colors#default-color-themes)`) - return getColor(defaultColor) + return null + } + + /** + * Returns color from config variables by name or return prop if color is a valid hex, hsl, hsla, rgb or rgba color. + * @param prop - should be color name or color in hex, hsl, hsla, rgb or rgba format. + * @param preferVariables - function should return (if possible) CSS variable instead of hex (hex is needed to set opacity). + * @param defaultColor - this color will be used if prop is invalid. By default it is primary color. + */ + const getColor = (prop?: string, defaultColor?: string, preferVariables?: boolean): CssColor => { + if (!defaultColor) { + defaultColor = colors.primary + } + + if (!prop) { return defaultColor } + + return getColorStrict(prop, preferVariables) ?? defaultColor } const getComputedColor = (color: string) => { @@ -125,7 +129,7 @@ export const useColors = () => { .keys(colors) .filter((key) => colors[key] !== undefined) .reduce((acc: Record, colorName: string) => { - acc[`--${prefix}-${kebabCase(colorName)}`] = getColor(colors[colorName], undefined, true) + acc[`--${prefix}-${kebabCase(colorName)}`] = getColor(colors[colorName]!, undefined, true) acc[`--${prefix}-on-${kebabCase(colorName)}`] = getColor(getTextColor(getColor(colors[colorName]!)), undefined, true) return acc }, {}) @@ -185,6 +189,7 @@ export const useColors = () => { applyPreset, setColors, getColors, + getColorStrict, getColor, getComputedColor, getBoxShadowColor, diff --git a/packages/ui/src/services/config-transport/createCSSVariables.ts b/packages/ui/src/services/config-transport/createCSSVariables.ts new file mode 100644 index 0000000000..fc3599513a --- /dev/null +++ b/packages/ui/src/services/config-transport/createCSSVariables.ts @@ -0,0 +1,33 @@ +import { ComponentInternalInstance, Ref, computed } from 'vue' +import { cssVariableName } from '../color/utils' +import { Props } from './shared' +import { useColors } from '../../composables' + +export const createCSSVariables = (instance: ComponentInternalInstance, attributes: Props) => { + const name = instance.type.name + + const { getColorStrict } = useColors() + + return computed(() => { + const keys = Object.keys(attributes) + + const variables = new Map() + + for (let i = 0; i < keys.length; i++) { + const key = keys[i] + let value = String(attributes[key]) + + if (!key.startsWith('style:')) { continue } + + const color = getColorStrict(value, true) + + if (color) { + value = color + } + + variables.set(cssVariableName(name?.slice(2) + '-' + key.slice(6)), String(value)) + } + + return variables + }) +} diff --git a/packages/ui/src/services/config-transport/createRenderFn.ts b/packages/ui/src/services/config-transport/createRenderFn.ts index e7113a882f..636f1a3d0e 100644 --- a/packages/ui/src/services/config-transport/createRenderFn.ts +++ b/packages/ui/src/services/config-transport/createRenderFn.ts @@ -1,4 +1,4 @@ -import { withCtx, h, DefineComponent, VNode, isVNode, Text, createBlock } from 'vue' +import { withCtx, h, DefineComponent, VNode, isVNode, Text, createBlock, ComputedRef, normalizeStyle } from 'vue' type VueInternalRenderFunction = Function @@ -49,6 +49,28 @@ export const createRenderFn = (component: DefineComponent): VueInternalRenderFun // When compile rendered function, it doesn't require thisArg const thisArg = compiledRenderedFn ? undefined : customCtx - return originalRenderFn.call(thisArg, customCtx, ...args.slice(1)) + const result: VNode = originalRenderFn.call(thisArg, customCtx, ...args.slice(1)) + + if ('ctx' in result) { + const variables: ComputedRef> = (result.ctx as any).$vaCssVaraibles + + if (!variables) { + return result + } + + if (result.props === null) { + result.props = {} + } + + const vars: Record = {} + + for (const key of variables.value.keys()) { + vars[key] = variables.value.get(key)! + } + + result.props.style = normalizeStyle([result.props.style, vars]) + } + + return result } } diff --git a/packages/ui/src/services/config-transport/createSetupFn.ts b/packages/ui/src/services/config-transport/createSetupFn.ts index c1caaaf50f..28f13d57b6 100644 --- a/packages/ui/src/services/config-transport/createSetupFn.ts +++ b/packages/ui/src/services/config-transport/createSetupFn.ts @@ -5,6 +5,7 @@ import { type Props } from './shared' import { createProps } from './createProps' import { createAttrs } from './createAttrs' import { createSlots } from './createSlots' +import { createCSSVariables } from './createCSSVariables' export const createSetupFn = (component: T) => { return (originalProps: Props, ctx: SetupContext) => { @@ -17,6 +18,7 @@ export const createSetupFn = (component: T) => { const props = createProps(instance, propsFromConfig) const attrs = createAttrs(instance, attrsFromConfig) const slots = createSlots(instance, propsFromConfig) + const cssVariables = createCSSVariables(instance, attrs) /** * Patch instance props with Proxy. @@ -25,6 +27,7 @@ export const createSetupFn = (component: T) => { instance.props = props instance.attrs = attrs instance.slots = slots + ;(instance as any).$vaCssVaraibles = cssVariables const setupState = component.setup?.(shallowReadonly(props), { ...ctx,