Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: style attribute #4261

Open
wants to merge 1 commit into
base: develop
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
11 changes: 10 additions & 1 deletion packages/ui/src/components/va-button/VaButton.stories.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { defineComponent } from 'vue'
import VaButton from './VaButton.vue'
import { VaButton } from './'
import VaButtonDemo from './VaButton.demo.vue'

export default {
Expand All @@ -22,3 +22,12 @@ export const RightShift = () => ({
</div>
`,
})

export const StyleAttributes = () => ({
components: { VaButton },
template: `
<div>
<VaButton style:background="danger">Text</VaButton>
</div>
`,
})
4 changes: 2 additions & 2 deletions packages/ui/src/components/va-button/VaButton.vue
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
37 changes: 21 additions & 16 deletions packages/ui/src/composables/useColors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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'
}
Expand All @@ -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
Expand All @@ -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) => {
Expand All @@ -125,7 +129,7 @@ export const useColors = () => {
.keys(colors)
.filter((key) => colors[key] !== undefined)
.reduce((acc: Record<string, any>, 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
}, {})
Expand Down Expand Up @@ -185,6 +189,7 @@ export const useColors = () => {
applyPreset,
setColors,
getColors,
getColorStrict,
getColor,
getComputedColor,
getBoxShadowColor,
Expand Down
33 changes: 33 additions & 0 deletions packages/ui/src/services/config-transport/createCSSVariables.ts
Original file line number Diff line number Diff line change
@@ -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<string, string>()

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
})
}
26 changes: 24 additions & 2 deletions packages/ui/src/services/config-transport/createRenderFn.ts
Original file line number Diff line number Diff line change
@@ -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

Expand Down Expand Up @@ -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<Map<string, string>> = (result.ctx as any).$vaCssVaraibles

if (!variables) {
return result
}

if (result.props === null) {
result.props = {}
}

const vars: Record<string, string> = {}

for (const key of variables.value.keys()) {
vars[key] = variables.value.get(key)!
}

result.props.style = normalizeStyle([result.props.style, vars])
}

return result
}
}
3 changes: 3 additions & 0 deletions packages/ui/src/services/config-transport/createSetupFn.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 = <T extends DefineComponent>(component: T) => {
return (originalProps: Props, ctx: SetupContext) => {
Expand All @@ -17,6 +18,7 @@ export const createSetupFn = <T extends DefineComponent>(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.
Expand All @@ -25,6 +27,7 @@ export const createSetupFn = <T extends DefineComponent>(component: T) => {
instance.props = props
instance.attrs = attrs
instance.slots = slots
;(instance as any).$vaCssVaraibles = cssVariables

const setupState = component.setup?.(shallowReadonly(props), {
...ctx,
Expand Down