import type { Meta, StoryObj } from '@storybook/vue3-vite'
import type { FormItem as FormItemType } from '@/types/settingTypes'
import FormItem from './FormItem.vue'
const meta: Meta = {
title: 'Components/Common/FormItem',
component: FormItem as any,
parameters: {
layout: 'centered',
docs: {
description: {
component:
'FormItem is a generalized form component that dynamically renders different input types based on configuration. Supports text, number, boolean, combo, slider, knob, color, image, and custom renderer inputs with proper labeling and accessibility.'
}
}
},
argTypes: {
item: {
control: 'object',
description:
'FormItem configuration object defining the input type and properties'
},
formValue: {
control: 'text',
description: 'The current form value (v-model)',
defaultValue: ''
},
id: {
control: 'text',
description: 'Optional HTML id for the form input',
defaultValue: undefined
},
labelClass: {
control: 'text',
description: 'Additional CSS classes for the label',
defaultValue: undefined
}
},
tags: ['autodocs']
}
export default meta
type Story = StoryObj
export const TextInput: Story = {
render: (args: any) => ({
components: { FormItem },
setup() {
return { args }
},
data() {
return {
value: args.formValue || 'Default text value',
textItem: {
name: 'Workflow Name',
type: 'text',
tooltip: 'Enter a descriptive name for your workflow',
attrs: {
placeholder: 'e.g., SDXL Portrait Generation'
}
} as FormItemType
}
},
methods: {
updateValue(newValue: string) {
console.log('Text value updated:', newValue)
this.value = newValue
}
},
template: `
Text input form item with tooltip:
Current value: "{{ value }}"
`
}),
args: {
formValue: 'My Workflow'
},
parameters: {
docs: {
description: {
story:
'Text input FormItem with tooltip and placeholder. Hover over the info icon to see the tooltip.'
}
}
}
}
export const NumberInput: Story = {
render: () => ({
components: { FormItem },
data() {
return {
value: 7.5,
numberItem: {
name: 'CFG Scale',
type: 'number',
tooltip:
'Classifier-free guidance scale controls how closely the AI follows your prompt',
attrs: {
min: 1,
max: 30,
step: 0.5,
showButtons: true
}
} as FormItemType
}
},
methods: {
updateValue(newValue: number) {
console.log('CFG scale updated:', newValue)
this.value = newValue
}
},
template: `
Number input with controls and constraints:
Current CFG scale: {{ value }}
`
}),
parameters: {
docs: {
description: {
story:
'Number input FormItem with min/max constraints and increment buttons for CFG scale parameter.'
}
}
}
}
export const BooleanToggle: Story = {
render: () => ({
components: { FormItem },
data() {
return {
value: false,
booleanItem: {
name: 'Enable GPU Acceleration',
type: 'boolean',
tooltip: 'Use GPU for faster processing when available'
} as FormItemType
}
},
methods: {
updateValue(newValue: boolean) {
console.log('GPU acceleration toggled:', newValue)
this.value = newValue
}
},
template: `
Boolean toggle switch form item:
GPU acceleration: {{ value ? 'Enabled' : 'Disabled' }}
`
}),
parameters: {
docs: {
description: {
story:
'Boolean FormItem using ToggleSwitch component for enable/disable settings.'
}
}
}
}
export const ComboSelect: Story = {
render: () => ({
components: { FormItem },
data() {
return {
value: 'euler_a',
comboItem: {
name: 'Sampling Method',
type: 'combo',
tooltip: 'Algorithm used for denoising during generation',
options: [
'euler_a',
'euler',
'heun',
'dpm_2',
'dpm_2_ancestral',
'lms',
'dpm_fast',
'dpm_adaptive',
'dpmpp_2s_ancestral',
'dpmpp_sde',
'dpmpp_2m'
]
} as FormItemType
}
},
methods: {
updateValue(newValue: string) {
console.log('Sampling method updated:', newValue)
this.value = newValue
}
},
template: `
Combo select with sampling methods:
Selected: {{ value }}
`
}),
parameters: {
docs: {
description: {
story:
'Combo select FormItem with ComfyUI sampling methods showing dropdown selection.'
}
}
}
}
export const SliderInput: Story = {
render: () => ({
components: { FormItem },
data() {
return {
value: 0.7,
sliderItem: {
name: 'Denoise Strength',
type: 'slider',
tooltip:
'How much to denoise the input image (0 = no change, 1 = complete redraw)',
attrs: {
min: 0,
max: 1,
step: 0.01
}
} as FormItemType
}
},
methods: {
updateValue(newValue: number) {
console.log('Denoise strength updated:', newValue)
this.value = newValue
}
},
template: `
Slider input with precise decimal control:
Denoise: {{ (value * 100).toFixed(0) }}%
`
}),
parameters: {
docs: {
description: {
story:
'Slider FormItem for denoise strength with percentage display and fine-grained control.'
}
}
}
}
export const KnobInput: Story = {
render: () => ({
components: { FormItem },
data() {
return {
value: 20,
knobItem: {
name: 'Sampling Steps',
type: 'knob',
tooltip:
'Number of denoising steps - more steps = higher quality but slower generation',
attrs: {
min: 1,
max: 150,
step: 1
}
} as FormItemType
}
},
methods: {
updateValue(newValue: number) {
console.log('Steps updated:', newValue)
this.value = newValue
}
},
template: `
Knob input for sampling steps:
Steps: {{ value }} ({{ value < 10 ? 'Very Fast' : value < 30 ? 'Fast' : value < 50 ? 'Balanced' : 'High Quality' }})
`
}),
parameters: {
docs: {
description: {
story:
'Knob FormItem for sampling steps with quality indicator based on step count.'
}
}
}
}
export const MultipleFormItems: Story = {
render: () => ({
components: { FormItem },
data() {
return {
widthValue: 512,
heightValue: 512,
stepsValue: 20,
cfgValue: 7.5,
samplerValue: 'euler_a',
hiresValue: false
}
},
computed: {
formItems() {
return [
{
name: 'Width',
type: 'number',
tooltip: 'Image width in pixels',
attrs: { min: 64, max: 2048, step: 64 }
},
{
name: 'Height',
type: 'number',
tooltip: 'Image height in pixels',
attrs: { min: 64, max: 2048, step: 64 }
},
{
name: 'Sampling Steps',
type: 'knob',
tooltip: 'Number of denoising steps',
attrs: { min: 1, max: 150, step: 1 }
},
{
name: 'CFG Scale',
type: 'slider',
tooltip: 'Classifier-free guidance scale',
attrs: { min: 1, max: 30, step: 0.5 }
},
{
name: 'Sampler',
type: 'combo',
tooltip: 'Sampling algorithm',
options: ['euler_a', 'euler', 'heun', 'dpm_2', 'dpmpp_2m']
},
{
name: 'High-res Fix',
type: 'boolean',
tooltip: 'Enable high-resolution generation'
}
] as FormItemType[]
},
allSettings() {
return {
width: this.widthValue,
height: this.heightValue,
steps: this.stepsValue,
cfg: this.cfgValue,
sampler: this.samplerValue,
enableHires: this.hiresValue
}
}
},
template: `
ComfyUI Generation Settings
Multiple form items demonstrating different input types in a realistic settings panel.
widthValue = value"
id="form-width"
/>
heightValue = value"
id="form-height"
/>
stepsValue = value"
id="form-steps"
/>
cfgValue = value"
id="form-cfg"
/>
samplerValue = value"
id="form-sampler"
/>
hiresValue = value"
id="form-hires"
/>
Current Settings:
{{ JSON.stringify(allSettings, null, 2) }}
`
}),
parameters: {
docs: {
description: {
story:
'Multiple FormItems demonstrating all major input types in a realistic ComfyUI settings panel.'
}
}
}
}
export const WithCustomLabels: Story = {
render: () => ({
components: { FormItem },
data() {
return {
value: 'custom_model.safetensors',
customItem: {
name: 'Model File',
type: 'text',
tooltip: 'Select the checkpoint model file to use for generation',
attrs: {
placeholder: 'Select or enter model filename...'
}
} as FormItemType
}
},
methods: {
updateValue(newValue: string) {
console.log('Model file updated:', newValue)
this.value = newValue
}
},
template: `
FormItem with custom label styling and slots:
*
Selected model: {{ value || 'None' }}
`
}),
parameters: {
docs: {
description: {
story:
'FormItem with custom label styling and prefix/suffix slots for enhanced UI elements.'
}
}
}
}
export const ColorPicker: Story = {
render: () => ({
components: { FormItem },
data() {
return {
value: '#3b82f6',
colorItem: {
name: 'Theme Accent Color',
type: 'color',
tooltip: 'Primary accent color for the interface theme'
} as FormItemType
}
},
methods: {
updateValue(newValue: string) {
console.log('Color updated:', newValue)
this.value = newValue
}
},
template: `
`
}),
parameters: {
docs: {
description: {
story:
'Color picker FormItem with live preview showing the selected color value.'
}
}
}
}
export const ComboWithComplexOptions: Story = {
render: () => ({
components: { FormItem },
data() {
return {
value: 'medium',
comboItem: {
name: 'Quality Preset',
type: 'combo',
tooltip:
'Predefined quality settings that adjust multiple parameters',
options: [
{ text: 'Draft (Fast)', value: 'draft' },
{ text: 'Medium Quality', value: 'medium' },
{ text: 'High Quality', value: 'high' },
{ text: 'Ultra (Slow)', value: 'ultra' }
]
} as FormItemType
}
},
methods: {
updateValue(newValue: string) {
console.log('Quality preset updated:', newValue)
this.value = newValue
}
},
computed: {
presetDescription() {
const descriptions = {
draft: 'Fast generation with 10 steps, suitable for previews',
medium: 'Balanced quality with 20 steps, good for most use cases',
high: 'High quality with 40 steps, slower but better results',
ultra: 'Maximum quality with 80 steps, very slow but best results'
}
return (descriptions as any)[this.value] || 'Unknown preset'
}
},
template: `
Combo with complex option objects:
`
}),
parameters: {
docs: {
description: {
story:
'Complex combo FormItem with object options showing text/value pairs and descriptions.'
}
}
}
}