Skip to content

houd1ni/fela-vue

Repository files navigation

fela-vue

Fela toolkit for Vue designed for flexibility yet team-oriented. website wip.

Build Status codecov bundlephobia npm Deps DevDeps (tree-shaking friendly!)

Fela does the great job, but it has no idea how to cook it with Vue. This is what I've created after combining vue's :style and :class attributes to make apps dynamically configured and easiest to write and maintain.

Included as deps:

The plugins are a lite most useful part of fela-preset-web.

USAGE

More about plugins. Several basic are already built in here!

More about enhancers.

In the options object below you can also add other Renderer options

Detailed API Docs are in separate markdowns below

// All of the options are optional.
const options = {
  // Default styles to mix. Does not mix if omitted.
  // Have a look at the example below to see it in action.
  // Either pass a function (then key would be `fdef`):
  defStyles: (componentInstance) => ({ colors: { cyan: 'cyan' }, bold: { fontWeight: 'bold' } }),
  // ... Or an object with your own key:
  defStyles: {
    key: 'fdef',
    value: (componentInstance) => ({ colors: { cyan: 'cyan' } })
  },
  // Modifiers to classes when assigned from templates. See examples below.
  // Each modifier is a condition that is being called with provided class name and context object.
  modifiers: {},
  // Name of styling method. Defaults to `f`.
  method: 'f',
  // Additional fela plugins.
  plugins: [],
  // Additional fela enhancers.
  enhancers: [],
  // Preset configurations.
  preset: {
    // Config for fela-plugin-unit. Same defaults ('px', {}).
    unit: ['em', { margin: '%' }]
  },
  // SSR status.
  ssr: false
}

const renderer = new Renderer(options)

// If Options API. To use globally:
Vue.mixin(renderer.mixin)
// or createApp(App).mixin(stylesRenderer.mixin)

// ... Or per module
export default {
  mixins: [ renderer.mixin ],
  // ...
}

EXAMPLES

** same options object as above **

// main.js
import Vue from 'vue'
import { Renderer } from 'fela-vue'

const renderer = new Renderer({
  ...options,
  modifiers: {
    // Too simple but short c:
    mobile: () => window.clientWidth < 600
  }
})

// Not required.
// Search for documentation link by "DOM helpers" here above.
renderer.setClasses(css`
  .html, .body {
    margin 0
    padding 0
    font-size 18px
  }
`)

// if Options API.
// Vue 2:
Vue.mixin( renderer.mixin )
// Vue 3:
// createApp(App)
//   .mixin((new Renderer(options)).mixin)
//   .mount('#app')

Component example

MyComponent.vue

<template>
  <div :class="f('wrapper')">
    <span :class="f('one')"> It's green! </span>
    <span :class="f('two')"> It's cyan! </span>
    <span :class="f('mobile&one !mobile.two localModifier.bold')">
      It's green when mobile modifier is true and cyan when false. `.` equals to `&` on your taste.
      For Options API the local modifiers named styleMods in mixins (see below in JS)
      For Composition API search for "with Vue Composition API" doc link above.
    </span>
    <span :class="f('three', {color: 'white'})"> you don't see me! </span>
    <span :class="f({color: 'yellow'})"> I do it by myself! </span>
    <span :class="f('one two, bold')"> Combined classes by commas and spaces </span>
    <span :class="f('bold my-kebab')"> And kebab-case! </span>
    <span :class="f('bold myKebab')"> The same! </span>
    <span :class="f('button one')">
      If class is not in local style(), it will be taken from defaults (defStyles), if present.
      Here's button could be taken from there, then merged with `one`
      where is `one` is in priority: right to left principle.
    </span>
    <div v-for="i in [0,1,2]">
      <span
        :class="f((i) => ({color: ['green', 'blue', 'yellow'][i]}))"
      > This way is OK too. </span>
    </div>
  </div>
</template>

<script>
// Uncomment to use literal css: css`...`
// import { css } from 'fela-vue'
import { defineComponent } from 'vue'

export default defineComponent({
  computed: {
    style() {
      // Not required. Use with arbitrary key by `options.defStyles.key`.
      const { colors } = this.fdef

      // Also, it's recommended to return one css`...` with all classes included.
      // Search for a "lit-css" documentation link above.
      // Also search for a "Vue Composition API" to use along it.
      return {
        one: { color: 'green' },
        two: { color: colors.cyan },
        three: ({color}) => {
          fontWeight: 'bold',
          color
        },
        bold: () => ({ fontWeight: 'bold' }),
        // 'my-kebab' is also valid if the same in the template.
        myKebab: { color: 'purple' },
        anotherClass: css`
          background: grey
        `,
        ...css`
          .other-class {
            margin-top: 44; // still ok for fela. will be 44px.
            // you can comment a whole line,
            margin-left: 22 // this's OK too.
            /* block comments are also supported. */
            :hover {
              // no colons and semicolons are ok.
              background grey
            }
          }
          .anotherOne {
            padding: 15
          }
        `
      }
    },
    styleMods() {
      return {
        localModifier: (name, context) => true
      }
    }
  }
})
</script>

It's better to make this computed in the end of a component definition or make a const variable at the bottom and return it from the computed prop.

Also, It's very handy to make snippets for adding style() {} to computed.