Skip to content

Adding a New Configuration Option Type

Tabulate edited this page Jul 29, 2026 · 6 revisions

Velour plugins can define different types of options. Numerous suboption types are provided, including but not limited to:

  • Checkboxes
  • Number Inputs
  • Text Inputs
  • Radio Buttons

However, you may want to go about adding a new option. The following guide details my process of adding the dropdown menu option to make following along a bit easier.

Introduce New Types

We must define the schema for the option type so that the application can take advantage of the TypeScript compiler.

In the file src/core/types/options.ts, the new option types must be added:

  1. Add the option name and the corresponding TypeScript type to SubOptionTypeMap:
export interface SubOptionTypeMap {
  // ...
  /** Dropdowns will return the one selected item */
  dropdown: string
}
  1. Define the option schema interface that extends BaseSubOption and specify any fields it needs:
/**
 * Dropdown list suboption schema definition
 *
 * @extends {BaseSubOption<'dropdown'>} Extends the base option fields with a type of dropdown
 */
export interface DropdownSubOption extends BaseSubOption<'dropdown'> {
  /** List of available options */
  options: SelectedOption[] // note that this type is already defined in options.ts
  /** The option that is selected by default */
  default: string
}
  1. Add it to the SubOptionSchema union:
export type SubOptionSchema = CheckboxSubOption | ... | DropdownSubOption
  1. Add a Default Value to SUB_OPTION_DEFAULTS
export const SUB_OPTION_DEFAULTS: SubOptionTypeMap = {
  checkbox: false,
  ...,
  dropdown: '', // since dropdowns are required to provide a default, this doesnt really matter
}

Introduce New Component

Now we need to craft the UI for the new component.

  1. Create a new appropriately named file in src/components/options/:
<!-- src/components/options/DropdownOption.vue -->
<script setup lang="ts">
import type { DropdownSubOption } from '@/core/types'
import { computed } from 'vue'

const model = defineModel<string>({ required: true })
const props = defineProps<{
  opt: DropdownSubOption
}>()

// check for any custom validation errors
const validationError = computed(() => {
  if (!props.opt.validate) return false
  return typeof props.opt.validate(model.value) === 'string'
})
</script>

<template>
  <label
    class="label mt-2 flex w-full cursor-pointer flex-col items-start gap-3 sm:flex-row sm:items-center"
  >
    <div class="flex flex-1 flex-col transition-colors hover:text-white">
      <span
        class="label-text whitespace-normal"
        :class="opt.description ? 'text-base font-semibold' : 'text-sm font-medium'"
      >
        {{ opt.label }}
      </span>
      <span
        v-if="opt.description"
        class="label-text text-xs leading-4 whitespace-normal opacity-70"
      >
        {{ opt.description }}
      </span>
    </div>

    <!-- this is the main thing that needs to be modified -->
    <select
      class="select select-sm select-bordered"
      :class="{ 'select-error': validationError }"
      v-model="model"
    >
      <option disabled value="">Select an option</option>
      <option v-for="choice in opt.options" :key="choice.value" :value="choice.value">
        {{ choice.label }}
      </option>
    </select>
  </label>
</template>
  1. Register the component in src/components/PluginOptionsCard.vue
const componentMap: Record<string, unknown> = {
  checkbox: CheckboxOption,
  ...,
  dropdown: DropdownOption, // add it here
}

Component Validation

The component needs validation rules to make sure no invalid input is being sent to the script generator. For example, in a number input you'd want to check if the user actually typed a valid number, and you'd want to check it's in range of the provided min/max, if any.

Find src/composables/usePlugins.ts, and within it, the computed validationErrors function. In this specific case, the dropdown doesn't need any extra validation, so we can just add it to the NOOP at the end of the switch statement. If we did need extra validation, we can just add a new case to the switch, however, we end up with this:

case 'radio':
case 'dropdown':  // this is new
case 'checkbox': {
  break
}

Update Test Suite

Velour has a test suite to ensure that all plugins generate valid input in all given configurations. In order to do this, each option must specify mock values to use during testing.

In src/__tests__/plugins-bash.spec.ts find the getPossibleSuboptionVariants function. Add a new case for your option, in this case, dropdown. Since a dropdown is similar to a radio button where the user just selects from a list of choices, you can just map over its defined options and return their values for the test runner to use:

function getPossibleSuboptionVariants(suboption: SubOptionSchema): any[] {
  switch (suboption.type) {
    case 'checkbox':
      // checkbox is true or false
      return [true, false]
    case 'dropdown':
    case 'radio':
      // every radio or dropdown option selected once
      return suboption.options.map((opt) => opt.value)
    // ...
  }
}

Clone this wiki locally