Skip to content

Commit

Permalink
feat(defaults): allow setting class and style (#16459)
Browse files Browse the repository at this point in the history
Closes #16896
Closes #17188

Co-authored-by: Yuchao <[email protected]>
Co-authored-by: Kael <[email protected]>
  • Loading branch information
3 people authored Apr 26, 2023
1 parent 0b50234 commit e3338d9
Show file tree
Hide file tree
Showing 110 changed files with 876 additions and 124 deletions.
156 changes: 150 additions & 6 deletions packages/docs/src/pages/en/features/global-configuration.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
---
emphasized: true
meta:
title: Global configuration
description: Vuetify.config is an object containing global configuration options that modify the bootstrapping of your project.
Expand Down Expand Up @@ -57,11 +58,154 @@ This is used internally by some components already:

[v-defaults-provider](/components/defaults-providers/) can be used to set defaults for components within a specific scope.

## Priority
## Global class and styles

When creating and mounting a component, Vuetify uses the following priority in determining which prop value to use:
<alert type="success">

1. Value set as prop value to the component itself
2. Value defined in component specific section of defaults configuration object
3. Value defined in global section of defaults configuration object
4. Value defined in the prop definition of the Vuetify component itself.
This feature was introduced in [v3.2.0 (Orion)](https://github.com/vuetifyjs/vuetify/releases/tag/v3.2.0)

</alert>

Define global classes and styles for all [built-in](/components/all/) components; including [virtual](/features/aliasing/#virtual-component-defaults) ones. This provides an immense amount of utility when building your application's design system and it reduces the amount of duplicated code in your templates.

Let's say that you want to set the **text-transform** of all [v-btn](/components/buttons/) components to `none`, but are not interested in using [SASS variables](/features/sass-variables/). By simply adding the **style** property to a component's default values, you are able to apply custom values to all instances of said component.

The following code example modifies the **text-transform** CSS property of all `<v-btn>` components:

```js { resource="src/plugins/vuetify.js" }
import { createVuetify } from 'vuetify'
import { VBtn } from 'vuetify/components/VBtn'

export default createVuetify({
defaults: {
VBtn: {
style: 'text-transform: none;',
},
},
})
```

As an alternative, apply utility classes instead to achieve the same effect:

```js { resource="src/plugins/vuetify.js" }
import { createVuetify } from 'vuetify'
import { VBtn } from 'vuetify/components/VBtn'

export default createVuetify({
defaults: {
VBtn: {
class: 'text-none',
},
},
})
```

Additionally, it works with any valid Vue value type such as objects and arrays:

```js { resource="src/plugins/vuetify.js" }
import { createVuetify } from 'vuetify'
import { VBtn } from 'vuetify/components/VBtn'

export default createVuetify({
defaults: {
VBtn: {
style: [{ textTransform: 'none' }],
},
},
})
```

### Using with virtual components

Whether you are developing a wrapper framework or just a design system for your application, [virtual components](/features/aliasing/#virtual-component-defaults) are a powerful ally. Within the Vuetify defaults system, classes and styles are treated just like regular props but instead of being overwritten at the template level, they are merged.

For example, lets create an alias of the [v-btn](/components/buttons/) component and modify some of its default values:

```js { resource="src/plugins/vuetify.js" }
import { createVuetify } from 'vuetify'
import { VBtn } from 'vuetify/components/VBtn'

export default createVuetify({
aliases: {
VBtnPrimary: VBtn,
},

defaults: {
VBtnPrimary: {
class: ['v-btn--primary', 'text-none'],
},
},
})
```

Now, use `<v-btn-primary>` in a template and apply a custom class:

```html
<template>
<v-btn-primary class="foobar">Foobar</v-btn-primary>
</template>
```

When compiled, the resulting HTML will contain both the globally defined classes and the custom one:

```html
<!-- Example HTML Output -->
<button class="v-btn v-btn--primary text-none foobar">Fizzbuzz</button>
```

This is particularly useful when you have multiple variants of a component that need individual classes to target:

```html { resource="src/components/HelloWorld.vue" }
<template>
<v-app>
<v-main>
<v-btn-primary>Primary</v-btn-primary>

<span class="mx-2" />

<v-btn-secondary>Secondary</v-btn-secondary>
</v-main>
</v-app>
</template>

<style>
.v-btn.v-btn--primary {
background: linear-gradient(to right, #ff8a00, #da1b60);
color: white;
}
.v-btn.v-btn--secondary {
background: linear-gradient(to right, #da1b60, #ff8a00);
color: white;
}
</style>
```

Keep in mind that virtual components do not inherit global class or styles from their extension. For example, the following Vuetify configuration uses a [v-chip](/components/chips/) as the alias for the virtual `<v-chip-primary>` component.

```js { resource="src/plugins/vuetify.js" }
import { createVuetify } from 'vuetify'
import { VChip } from 'vuetify/components/VChip'

export default createVuetify({
aliases: {
VChipPrimary: VChip,
},

defaults: {
VChipPrimary: {
class: 'v-chip--primary',
},
VChip: {
class: 'v-chip--custom',
},
},
})
```

When `<v-chip-primary>` is used in a template, it will **not** have the `v-chip--custom` class.

<alert type="warning">

There are some cases where a default class or style could be unintentionally passed down to an inner component. This mostly concerns [form inputs and controls](/components/all/#form-inputs-and-controls).

</alert>
2 changes: 1 addition & 1 deletion packages/vuetify/cypress/templates/Application.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { VApp } from '@/components/VApp'
import { VLocaleProvider } from '@/components/VLocaleProvider'
import { FunctionalComponent } from 'vue'
import type { FunctionalComponent } from 'vue'

export const Application: FunctionalComponent<{ rtl?: boolean }> = (props, { slots, attrs }) => {
return (
Expand Down
6 changes: 5 additions & 1 deletion packages/vuetify/src/components/VAlert/VAlert.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ import { VIcon } from '@/components/VIcon'

// Composables
import { genOverlays, makeVariantProps, useVariant } from '@/composables/variant'
import { IconValue } from '@/composables/icons'
import { makeComponentProps } from '@/composables/component'
import { makeDensityProps, useDensity } from '@/composables/density'
import { makeDimensionProps, useDimension } from '@/composables/dimensions'
import { makeElevationProps, useElevation } from '@/composables/elevation'
Expand All @@ -20,7 +22,6 @@ import { makeThemeProps, provideTheme } from '@/composables/theme'
import { useLocale } from '@/composables/locale'
import { useProxiedModel } from '@/composables/proxiedModel'
import { useTextColor } from '@/composables/color'
import { IconValue } from '@/composables/icons'

// Utilities
import { computed, toRef } from 'vue'
Expand Down Expand Up @@ -84,6 +85,7 @@ export const VAlert = genericComponent<VAlertSlots>()({
validator: (val: ContextualType) => allowedTypes.includes(val),
},

...makeComponentProps(),
...makeDensityProps(),
...makeDimensionProps(),
...makeElevationProps(),
Expand Down Expand Up @@ -157,11 +159,13 @@ export const VAlert = genericComponent<VAlertSlots>()({
positionClasses.value,
roundedClasses.value,
variantClasses.value,
props.class,
]}
style={[
colorStyles.value,
dimensionStyles.value,
locationStyles.value,
props.style,
]}
role="alert"
>
Expand Down
8 changes: 7 additions & 1 deletion packages/vuetify/src/components/VApp/VApp.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import './VApp.sass'

// Composables
import { createLayout, makeLayoutProps } from '@/composables/layout'
import { makeComponentProps } from '@/composables/component'
import { makeThemeProps, provideTheme } from '@/composables/theme'
import { useRtl } from '@/composables/locale'

Expand All @@ -13,6 +14,7 @@ export const VApp = genericComponent()({
name: 'VApp',

props: {
...makeComponentProps(),
...makeLayoutProps({ fullHeight: true }),
...makeThemeProps(),
},
Expand All @@ -30,8 +32,12 @@ export const VApp = genericComponent()({
theme.themeClasses.value,
layoutClasses.value,
rtlClasses.value,
props.class,
]}
style={[
layoutStyles.value,
props.style,
]}
style={ layoutStyles.value }
>
<div class="v-application__wrap">
{ slots.default?.() }
Expand Down
16 changes: 10 additions & 6 deletions packages/vuetify/src/components/VAppBar/VAppBar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -147,13 +147,17 @@ export const VAppBar = genericComponent<VToolbarSlots>()({
{
'v-app-bar--bottom': props.location === 'bottom',
},
props.class,
]}
style={[
{
...layoutItemStyles.value,
'--v-toolbar-image-opacity': opacity.value,
height: undefined,
...ssrBootStyles.value,
},
props.style,
]}
style={{
...layoutItemStyles.value,
'--v-toolbar-image-opacity': opacity.value,
height: undefined,
...ssrBootStyles.value,
}}
{ ...toolbarProps }
collapse={ isCollapsed.value }
flat={ isFlat.value }
Expand Down
9 changes: 8 additions & 1 deletion packages/vuetify/src/components/VAppBar/VAppBarNavIcon.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { VBtn } from '@/components/VBtn'

// Composables
import { IconValue } from '@/composables/icons'
import { makeComponentProps } from '@/composables/component'

// Utilities
import { genericComponent, useRender } from '@/util'
Expand All @@ -18,13 +19,19 @@ export const VAppBarNavIcon = genericComponent<VBtnSlots>()({
type: IconValue,
default: '$menu',
},

...makeComponentProps(),
},

setup (props, { slots }) {
useRender(() => (
<VBtn
class="v-app-bar-nav-icon"
class={[
'v-app-bar-nav-icon',
props.class,
]}
icon={ props.icon }
style={ props.style }
v-slots={ slots }
/>
))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -338,7 +338,9 @@ export const VAutocomplete = genericComponent<new <
[`v-autocomplete--${props.multiple ? 'multiple' : 'single'}`]: true,
'v-autocomplete--selection-slot': !!slots.selection,
},
props.class,
]}
style={ props.style }
appendInnerIcon={ props.menuIcon }
readonly={ props.readonly }
placeholder={ isDirty ? undefined : props.placeholder }
Expand Down
4 changes: 4 additions & 0 deletions packages/vuetify/src/components/VAvatar/VAvatar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import { VImg } from '@/components/VImg'
// Composables
import { genOverlays, makeVariantProps, useVariant } from '@/composables/variant'
import { IconValue } from '@/composables/icons'
import { makeComponentProps } from '@/composables/component'
import { makeDensityProps, useDensity } from '@/composables/density'
import { makeRoundedProps, useRounded } from '@/composables/rounded'
import { makeSizeProps, useSize } from '@/composables/size'
Expand All @@ -23,6 +24,7 @@ export const makeVAvatarProps = propsFactory({
icon: IconValue,
image: String,

...makeComponentProps(),
...makeDensityProps(),
...makeRoundedProps(),
...makeSizeProps(),
Expand Down Expand Up @@ -57,10 +59,12 @@ export const VAvatar = genericComponent()({
roundedClasses.value,
sizeClasses.value,
variantClasses.value,
props.class,
]}
style={[
colorStyles.value,
sizeStyles.value,
props.style,
]}
>
{ props.image
Expand Down
4 changes: 4 additions & 0 deletions packages/vuetify/src/components/VBadge/VBadge.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { VIcon } from '@/components/VIcon'

// Composables
import { IconValue } from '@/composables/icons'
import { makeComponentProps } from '@/composables/component'
import { makeLocationProps, useLocation } from '@/composables/location'
import { makeRoundedProps, useRounded } from '@/composables/rounded'
import { makeTagProps } from '@/composables/tag'
Expand Down Expand Up @@ -52,6 +53,7 @@ export const VBadge = genericComponent<VBadgeSlots>()({
offsetY: [Number, String],
textColor: String,

...makeComponentProps(),
...makeLocationProps({ location: 'top end' } as const),
...makeRoundedProps(),
...makeTagProps(),
Expand Down Expand Up @@ -102,8 +104,10 @@ export const VBadge = genericComponent<VBadgeSlots>()({
'v-badge--floating': props.floating,
'v-badge--inline': props.inline,
},
props.class,
]}
{ ...attrs }
style={ props.style }
>
<div class="v-badge__wrapper">
{ ctx.slots.default?.() }
Expand Down
Loading

0 comments on commit e3338d9

Please sign in to comment.