Themes

A theme is a container of variants. A variant a set of overrides that can be applied to a component. You can define as many themes as you want, each containing as many variants as you want. All of that dynamically, whenever you want, using the VueThemes class.

VueThemes

VueThemes a simple static class that you can import anywhere in your project:

import { VueThemes } from "@banquette/vue-typescript";

If contains the following methods:

Method Description
Shorthand to define multiple variants at once, as an object.
Get the current global theme.
Get a specific theme by name and create it if it doesn't exist yet.
Check if a theme is defined.
Remove a theme.
Subscribe to an event that will trigger each time a new theme is added.
Subscribe to an event that will trigger each time the current theme changes.

The Define method is the one you should consider using to define a theme because that's the more convenient. Here is an example that illustrates all the possible options:

// We define a theme for all components named "my-button".
VueThemes.Define('my-button', {
    // The key is the name of the theme for which we will define variants.
    light: [
        // Then we define a list of variants, each having a "match" selector
        // and a set of rules to apply.
        {
            id: 'outline', // Unique id of the variant (optional), used by "apply".

            // The css variables to inject if the variant matches.
            cssVars: {
                backgroundColor: 'none'
            },
        },
        {
            id: 'danger', 
            match: 'danger', // The rules to match.
            
            cssVars: {
                backgroundColor: 'red'
            },
            
            // The default value to assign to the props.
            props: {
                type: 'primary' // Assign "primary" as default value for the prop "type".
            },

            // Ids of other variants to apply if this one matches.
            apply: ['outline'],
            
            // An object where each key is the name of a selector exposed by the component
            // and the value is a key/value pair of CSS rules/values.
            cssSelectors: {
                root: {
                    // Here you can put ANY css rule you want.
                    // What you put here is directly converted into CSS code.
                    // Each key must be a camelCased version of the CSS rule.
                    zIndex: 100,
                    fontFamily: 'Arial',
                    borderRadius: 'var(--base-border-radius)'
                }
            },
            
            // Raw css code that will be scoped automatically
            // to only apply to the targeted component in the current theme and variant.
            cssCode: '.title { color: blue }'
        },
        {
            match: '*' // Matches everything, always applies.
        }
    ],
    dark: [
        ...
    ],
    // Wildcard theme, always applies.
    '*': [
        ...
    ]
});

TIP

You can put * as theme name or variant name to say that you want it to apply to every theme or variant, no matter their name. It's a wildcard.

Theme matching

The current theme can be set in two ways:

  1. globally, using the VueThemes class
  2. locally, using the bt-theme component.

To set it globally:

import { VueThemes } from "@banquette/vue-typescript";

// You can call this anywhere, anytime.
// All components will automatically adjust.
VueThemes.SetCurrent('dark');

TIP

You can even call SetCurrent before creating the theme or before defining its variants. It will apply as soon as it can.


To set it locally:

You can set a theme for just a part of your page using the bt-theme component:

    Variant matching

    Each variant you define in your theme has a match attribute that defines the rules the component must match for it to be applied. This is called a VariantSelector, and offers the following criteria:

    Match on variant

    {
        match: {variant: 'danger outline'},
        [...]
    }
    

    Each variant is separated by a space, the component will have to have all of them for the variant to apply. The order in which they are defined doesn't matter.

    TIP

    variant is the default option for the variant selector, so the following is equivalent:

    {
        match: 'danger outline',
        [...]
    }
    

    Match on parent

    You can filter your variants based on their parent:

    {
        // The rules will apply if the component is inside a `bt-button`.
        match: {parent: 'bt-button'},
        [...]
    }
    
    <bt-button>
        <!-- Will match -->
        <my-component></my-component>
    </bt-button>
    
    <bt-button>
        <other>
            <!-- Will match too -->
            <my-component></my-component>
        </other>
    </bt-button>
    
    <!-- Will not -->
    <my-component></my-component>
    

    WARNING

    This only works with components created with VueTypescript because for other components there is no way to know their name from the $parent attribute.

    But there is more. parent is actually a VariantSelector, so everything you can do with match, you can also with parent:

    {
        match: {
            // The rules only apply if the component is inside a `bt-button` 
            // with a "danger" variant.
            parent: {name: 'bt-button', variant: 'danger'}
        },
        [...]
    }
    

    NOTE

    Just like for variant, the default option of parent is its name, that's why you can do:

    {
        // The following:
        match: {parent: 'bt-button'},
    
        // is equivalent to:
        match: {parent: {name: 'bt-button'}}
    }
    

    Match on props

    You can also match on the props of the component:

    {
        // The rules will apply if the component has a prop `disabled` with the value `true`. 
        match: {props: {disabled: true}},
    }
    
    <my-component :disabled="true"></my-component>
    

    Match on attributes

    The same goes for HTML attributes:

    {
        // The rules will apply if the component has an HTML attribute 
        // `data-custom` with the value `value`. 
        match: {attrs: {'data-custom': 'value'}},
    }
    
    <my-component data-custom="value"></my-component>
    

    Props and attrs callbacks

    Both props and attrs can take a callback as parameter, so you can test more complex testing that juste equality:

    {
        match: [{props: {progress: (v) => v < 40}}],
        [...]
    }
    

    The callback takes the current value of the prop or attribute being tested and returns a boolean.

    Putting it all together

    Here is an example of all we've discussed so far working together: