[feat] Add Storybook configuration and settings panel stories

- Set up Storybook 9.1.2 with Vue 3, PrimeVue, and Tailwind CSS support
- Created comprehensive stories for settings panel components:
  - SettingItem: Various input types (boolean, text, number, slider, combo, color)
  - SettingGroup: Grouped settings with dividers and different categories
  - SettingsPanel: Multiple setting groups and empty states
  - PanelTemplate: Reusable panel layout with header/footer slots
  - AboutPanel: System information and project badges
  - SettingDialogContent: Complete dialog with responsive layouts
- Updated TypeScript configuration to include Storybook files

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
snomiao
2025-08-16 06:20:42 +00:00
parent 26dbbb1395
commit 945e8e9984
12 changed files with 1169 additions and 186 deletions

View File

@@ -0,0 +1,108 @@
import type { Meta, StoryObj } from '@storybook/vue3-vite'
import SettingDialogContent from './SettingDialogContent.vue'
const meta: Meta<typeof SettingDialogContent> = {
title: 'Components/Dialog/SettingDialogContent',
component: SettingDialogContent,
parameters: {
layout: 'fullscreen',
docs: {
description: {
component:
'The complete settings dialog content with sidebar navigation and tabbed panels.'
}
}
},
argTypes: {
defaultPanel: {
control: 'select',
options: [
'about',
'keybinding',
'extension',
'server-config',
'user',
'credits'
],
description: 'The default panel to show when the dialog opens'
}
},
decorators: [
() => ({
template:
'<div style="width: 100vw; height: 100vh; padding: 20px; background: #f5f5f5;"><story /></div>'
})
]
}
export default meta
type Story = StoryObj<typeof meta>
export const Default: Story = {
args: {}
}
export const AboutPanel: Story = {
args: {
defaultPanel: 'about'
}
}
export const KeybindingPanel: Story = {
args: {
defaultPanel: 'keybinding'
}
}
export const ExtensionPanel: Story = {
args: {
defaultPanel: 'extension'
}
}
export const ServerConfigPanel: Story = {
args: {
defaultPanel: 'server-config'
}
}
export const UserPanel: Story = {
args: {
defaultPanel: 'user'
}
}
export const CreditsPanel: Story = {
args: {
defaultPanel: 'credits'
}
}
// Responsive variants
export const Mobile: Story = {
args: {},
parameters: {
viewport: {
defaultViewport: 'mobile1'
}
}
}
export const Tablet: Story = {
args: {},
parameters: {
viewport: {
defaultViewport: 'tablet'
}
}
}
export const Desktop: Story = {
args: {},
parameters: {
viewport: {
defaultViewport: 'desktop'
}
}
}

View File

@@ -0,0 +1,63 @@
import type { Meta, StoryObj } from '@storybook/vue3-vite'
import AboutPanel from './AboutPanel.vue'
const meta: Meta<typeof AboutPanel> = {
title: 'Components/Setting/AboutPanel',
component: AboutPanel,
parameters: {
layout: 'padded',
docs: {
description: {
component:
'The About panel displays project information, badges, and system statistics.'
}
}
},
decorators: [
() => ({
template:
'<div style="max-width: 600px; min-height: 400px; padding: 16px;"><story /></div>'
})
]
}
export default meta
type Story = StoryObj<typeof meta>
export const Default: Story = {
args: {},
parameters: {
docs: {
description: {
story:
'The default About panel showing project badges and system information.'
}
}
}
}
export const WithSystemStats: Story = {
args: {},
parameters: {
docs: {
description: {
story: 'About panel with system statistics visible.'
}
}
}
}
export const Mobile: Story = {
args: {},
parameters: {
viewport: {
defaultViewport: 'mobile1'
},
docs: {
description: {
story: 'About panel optimized for mobile devices.'
}
}
}
}

View File

@@ -0,0 +1,197 @@
import type { Meta, StoryObj } from '@storybook/vue3-vite'
import PanelTemplate from './PanelTemplate.vue'
const meta: Meta<typeof PanelTemplate> = {
title: 'Components/Setting/PanelTemplate',
component: PanelTemplate,
parameters: {
layout: 'padded',
docs: {
description: {
component:
'A template component for settings panels that provides consistent layout with header, content, and footer slots.'
}
}
},
argTypes: {
value: {
control: 'text',
description: 'The value identifier for the tab panel'
},
class: {
control: 'text',
description: 'Additional CSS classes to apply'
}
},
decorators: [
() => ({
template:
'<div style="width: 600px; height: 400px; border: 1px solid #ddd;"><story /></div>'
})
]
}
export default meta
type Story = StoryObj<typeof meta>
export const Default: Story = {
args: {
value: 'example-panel'
},
render: (args) => ({
components: { PanelTemplate },
setup() {
return { args }
},
template: `
<PanelTemplate v-bind="args">
<div class="p-4">
<h3 class="text-lg font-semibold mb-4">Panel Content</h3>
<p class="mb-4">This is the main content area of the panel.</p>
<div class="space-y-2">
<div class="p-2 bg-gray-100 rounded">Setting Item 1</div>
<div class="p-2 bg-gray-100 rounded">Setting Item 2</div>
<div class="p-2 bg-gray-100 rounded">Setting Item 3</div>
</div>
</div>
</PanelTemplate>
`
})
}
export const WithHeader: Story = {
args: {
value: 'header-panel'
},
render: (args) => ({
components: { PanelTemplate },
setup() {
return { args }
},
template: `
<PanelTemplate v-bind="args">
<template #header>
<div class="p-3 bg-blue-50 border border-blue-200 rounded mb-4">
<h4 class="text-blue-800 font-medium">Panel Header</h4>
<p class="text-blue-600 text-sm">This is a header message for the panel.</p>
</div>
</template>
<div class="p-4">
<h3 class="text-lg font-semibold mb-4">Panel Content</h3>
<p>Content with a header message above.</p>
</div>
</PanelTemplate>
`
})
}
export const WithFooter: Story = {
args: {
value: 'footer-panel'
},
render: (args) => ({
components: { PanelTemplate },
setup() {
return { args }
},
template: `
<PanelTemplate v-bind="args">
<div class="p-4">
<h3 class="text-lg font-semibold mb-4">Panel Content</h3>
<p>Content with a footer below.</p>
</div>
<template #footer>
<div class="p-3 bg-gray-50 border-t">
<div class="flex justify-end space-x-2">
<button class="px-4 py-2 bg-gray-500 text-white rounded hover:bg-gray-600">
Cancel
</button>
<button class="px-4 py-2 bg-blue-500 text-white rounded hover:bg-blue-600">
Save
</button>
</div>
</div>
</template>
</PanelTemplate>
`
})
}
export const WithHeaderAndFooter: Story = {
args: {
value: 'full-panel'
},
render: (args) => ({
components: { PanelTemplate },
setup() {
return { args }
},
template: `
<PanelTemplate v-bind="args">
<template #header>
<div class="p-3 bg-green-50 border border-green-200 rounded mb-4">
<h4 class="text-green-800 font-medium">Important Notice</h4>
<p class="text-green-600 text-sm">Please review all settings before saving.</p>
</div>
</template>
<div class="p-4">
<h3 class="text-lg font-semibold mb-4">Settings Content</h3>
<div class="space-y-4">
<div>
<label class="block text-sm font-medium mb-1">Setting 1</label>
<input type="text" class="w-full p-2 border rounded" value="Default value" />
</div>
<div>
<label class="block text-sm font-medium mb-1">Setting 2</label>
<select class="w-full p-2 border rounded">
<option>Option 1</option>
<option>Option 2</option>
</select>
</div>
</div>
</div>
<template #footer>
<div class="p-3 bg-gray-50 border-t">
<div class="flex justify-between items-center">
<span class="text-sm text-gray-600">Changes will be saved automatically</span>
<button class="px-4 py-2 bg-blue-500 text-white rounded hover:bg-blue-600">
Apply Changes
</button>
</div>
</div>
</template>
</PanelTemplate>
`
})
}
export const LongContent: Story = {
args: {
value: 'long-panel'
},
render: (args) => ({
components: { PanelTemplate },
setup() {
return { args }
},
template: `
<PanelTemplate v-bind="args">
<div class="p-4">
<h3 class="text-lg font-semibold mb-4">Scrollable Content</h3>
<div class="space-y-4">
${Array.from(
{ length: 20 },
(_, i) => `
<div class="p-3 bg-gray-100 rounded">
<h4 class="font-medium">Setting Group ${i + 1}</h4>
<p class="text-sm text-gray-600">This is setting group ${i + 1} with some description text.</p>
</div>
`
).join('')}
</div>
</div>
</PanelTemplate>
`
})
}

View File

@@ -0,0 +1,150 @@
import type { Meta, StoryObj } from '@storybook/vue3-vite'
import type { SettingParams } from '@/types/settingTypes'
import SettingGroup from './SettingGroup.vue'
const meta: Meta<typeof SettingGroup> = {
title: 'Components/Setting/SettingGroup',
component: SettingGroup,
parameters: {
layout: 'padded'
},
argTypes: {
group: {
control: 'object',
description: 'The setting group configuration'
},
divider: {
control: 'boolean',
description: 'Show divider above the group'
}
},
decorators: [
() => ({
template: '<div style="max-width: 500px; padding: 16px;"><story /></div>'
})
]
}
export default meta
type Story = StoryObj<typeof meta>
const createMockSetting = (
overrides: Partial<SettingParams> = {}
): SettingParams => ({
id: 'test.setting' as any,
name: 'Test Setting',
type: 'boolean',
defaultValue: false,
...overrides
})
export const BasicGroup: Story = {
args: {
group: {
label: 'Basic Settings',
settings: [
createMockSetting({
id: 'basic.enable' as any,
name: 'Enable Feature',
type: 'boolean',
defaultValue: true,
tooltip: 'Enable or disable this feature'
}),
createMockSetting({
id: 'basic.name' as any,
name: 'Display Name',
type: 'text',
defaultValue: 'My App',
tooltip: 'The name to display in the title bar'
})
]
},
divider: false
}
}
export const GroupWithDivider: Story = {
args: {
group: {
label: 'Advanced Settings',
settings: [
createMockSetting({
id: 'advanced.debug' as any,
name: 'Debug Mode',
type: 'boolean',
defaultValue: false,
experimental: true,
tooltip: 'Enable debug logging and developer tools'
}),
createMockSetting({
id: 'advanced.timeout' as any,
name: 'Request Timeout (ms)',
type: 'number',
defaultValue: 5000,
tooltip: 'How long to wait for requests',
attrs: {
min: 1000,
max: 30000,
step: 500
}
})
]
},
divider: true
}
}
export const PerformanceGroup: Story = {
args: {
group: {
label: 'Performance',
settings: [
createMockSetting({
id: 'perf.threads' as any,
name: 'Worker Threads',
type: 'slider',
defaultValue: 4,
tooltip: 'Number of worker threads to use',
attrs: {
min: 1,
max: 16,
step: 1
}
}),
createMockSetting({
id: 'perf.quality' as any,
name: 'Render Quality',
type: 'combo',
defaultValue: 'high',
tooltip: 'Rendering quality level',
options: [
{ text: 'Low', value: 'low' },
{ text: 'Medium', value: 'medium' },
{ text: 'High', value: 'high' },
{ text: 'Ultra', value: 'ultra' }
]
}),
createMockSetting({
id: 'perf.vsync' as any,
name: 'V-Sync',
type: 'boolean',
defaultValue: true,
tooltip: 'Enable vertical synchronization'
})
]
},
divider: false
}
}
export const EmptyGroup: Story = {
args: {
group: {
label: 'Empty Group',
settings: []
},
divider: false
}
}

View File

@@ -0,0 +1,159 @@
import type { Meta, StoryObj } from '@storybook/vue3-vite'
import type { SettingParams } from '@/types/settingTypes'
import SettingItem from './SettingItem.vue'
const meta: Meta<typeof SettingItem> = {
title: 'Components/Setting/SettingItem',
component: SettingItem,
parameters: {
layout: 'padded'
},
argTypes: {
setting: {
control: 'object',
description: 'The setting configuration object'
}
},
decorators: [
() => ({
template: '<div style="max-width: 500px; padding: 16px;"><story /></div>'
})
]
}
export default meta
type Story = StoryObj<typeof meta>
const createMockSetting = (
overrides: Partial<SettingParams> = {}
): SettingParams => ({
id: 'test.setting' as any,
name: 'Test Setting',
type: 'boolean',
defaultValue: false,
tooltip: 'This is a test setting for demonstration purposes',
...overrides
})
export const BooleanSetting: Story = {
args: {
setting: createMockSetting({
name: 'Enable Feature',
type: 'boolean',
defaultValue: true,
tooltip: 'Toggle this feature on or off'
})
}
}
export const TextSetting: Story = {
args: {
setting: createMockSetting({
name: 'API Endpoint',
type: 'text',
defaultValue: 'https://api.example.com',
tooltip: 'The API endpoint to connect to'
})
}
}
export const NumberSetting: Story = {
args: {
setting: createMockSetting({
name: 'Max Connections',
type: 'number',
defaultValue: 10,
tooltip: 'Maximum number of concurrent connections',
attrs: {
min: 1,
max: 100,
step: 1
}
})
}
}
export const SliderSetting: Story = {
args: {
setting: createMockSetting({
name: 'Volume Level',
type: 'slider',
defaultValue: 50,
tooltip: 'Adjust the volume level',
attrs: {
min: 0,
max: 100,
step: 5
}
})
}
}
export const ComboSetting: Story = {
args: {
setting: createMockSetting({
name: 'Theme',
type: 'combo',
defaultValue: 'dark',
tooltip: 'Select your preferred theme',
options: [
{ text: 'Light', value: 'light' },
{ text: 'Dark', value: 'dark' },
{ text: 'Auto', value: 'auto' }
]
})
}
}
export const ColorSetting: Story = {
args: {
setting: createMockSetting({
name: 'Accent Color',
type: 'color',
defaultValue: '#007bff',
tooltip: 'Choose your accent color'
})
}
}
export const ExperimentalSetting: Story = {
args: {
setting: createMockSetting({
name: 'Experimental Feature',
type: 'boolean',
defaultValue: false,
experimental: true,
tooltip: 'This feature is experimental and may change'
})
}
}
export const WithLanguageTag: Story = {
args: {
setting: createMockSetting({
id: 'Comfy.Locale' as any,
name: 'Language',
type: 'combo',
defaultValue: 'en',
tooltip: 'Select your preferred language',
options: [
{ text: 'English', value: 'en' },
{ text: 'Spanish', value: 'es' },
{ text: 'French', value: 'fr' }
]
})
}
}
export const InteractiveBoolean: Story = {
args: {
setting: createMockSetting({
name: 'Interactive Boolean',
type: 'boolean',
defaultValue: false,
tooltip: 'Click to toggle this setting'
})
}
}

View File

@@ -0,0 +1,238 @@
import type { Meta, StoryObj } from '@storybook/vue3-vite'
import type { ISettingGroup, SettingParams } from '@/types/settingTypes'
import SettingsPanel from './SettingsPanel.vue'
const meta: Meta<typeof SettingsPanel> = {
title: 'Components/Setting/SettingsPanel',
component: SettingsPanel,
parameters: {
layout: 'padded'
},
argTypes: {
settingGroups: {
control: 'object',
description: 'Array of setting groups to display'
}
},
decorators: [
() => ({
template: '<div style="max-width: 600px; padding: 16px;"><story /></div>'
})
]
}
export default meta
type Story = StoryObj<typeof meta>
const createMockSetting = (
overrides: Partial<SettingParams> = {}
): SettingParams => ({
id: 'test.setting' as any,
name: 'Test Setting',
type: 'boolean',
defaultValue: false,
...overrides
})
const mockGeneralSettings: ISettingGroup = {
label: 'General',
settings: [
createMockSetting({
id: 'Comfy.Locale' as any,
name: 'Language',
type: 'combo',
defaultValue: 'en',
tooltip: 'Select your preferred language',
options: [
{ text: 'English', value: 'en' },
{ text: 'Spanish', value: 'es' },
{ text: 'French', value: 'fr' },
{ text: 'German', value: 'de' }
]
}),
createMockSetting({
id: 'Comfy.AutoSave' as any,
name: 'Auto Save',
type: 'boolean',
defaultValue: true,
tooltip: 'Automatically save your work'
}),
createMockSetting({
id: 'Comfy.AutoSaveInterval' as any,
name: 'Auto Save Interval (seconds)',
type: 'number',
defaultValue: 30,
tooltip: 'How often to auto save in seconds',
attrs: {
min: 10,
max: 300,
step: 5
}
})
]
}
const mockAppearanceSettings: ISettingGroup = {
label: 'Appearance',
settings: [
createMockSetting({
id: 'Comfy.ColorPalette' as any,
name: 'Color Palette',
type: 'combo',
defaultValue: 'dark',
tooltip: 'Choose your color theme',
options: [
{ text: 'Dark', value: 'dark' },
{ text: 'Light', value: 'light' },
{ text: 'Arc', value: 'arc' },
{ text: 'Nord', value: 'nord' }
]
}),
createMockSetting({
id: 'Comfy.AccentColor' as any,
name: 'Accent Color',
type: 'color',
defaultValue: '#007bff',
tooltip: 'Choose your accent color'
}),
createMockSetting({
id: 'Comfy.NodeOpacity' as any,
name: 'Node Opacity',
type: 'slider',
defaultValue: 80,
tooltip: 'Adjust node transparency',
attrs: {
min: 10,
max: 100,
step: 10
}
})
]
}
const mockPerformanceSettings: ISettingGroup = {
label: 'Performance',
settings: [
createMockSetting({
id: 'Comfy.MaxConcurrentTasks' as any,
name: 'Max Concurrent Tasks',
type: 'number',
defaultValue: 4,
tooltip: 'Maximum number of tasks to run simultaneously',
attrs: {
min: 1,
max: 16
}
}),
createMockSetting({
id: 'Comfy.EnableGPUAcceleration' as any,
name: 'GPU Acceleration',
type: 'boolean',
defaultValue: true,
tooltip: 'Enable GPU acceleration for better performance',
experimental: true
}),
createMockSetting({
id: 'Comfy.CacheSize' as any,
name: 'Cache Size (MB)',
type: 'slider',
defaultValue: 512,
tooltip: 'Amount of memory to use for caching',
attrs: {
min: 128,
max: 2048,
step: 128
}
})
]
}
export const EmptyPanel: Story = {
args: {
settingGroups: []
}
}
export const SingleGroup: Story = {
args: {
settingGroups: [mockGeneralSettings]
}
}
export const MultipleGroups: Story = {
args: {
settingGroups: [
mockGeneralSettings,
mockAppearanceSettings,
mockPerformanceSettings
]
}
}
export const AppearanceOnly: Story = {
args: {
settingGroups: [mockAppearanceSettings]
}
}
export const PerformanceOnly: Story = {
args: {
settingGroups: [mockPerformanceSettings]
}
}
export const MixedInputTypes: Story = {
args: {
settingGroups: [
{
label: 'Mixed Settings',
settings: [
createMockSetting({
id: 'mixed.boolean' as any,
name: 'Boolean Setting',
type: 'boolean',
defaultValue: true
}),
createMockSetting({
id: 'mixed.text' as any,
name: 'Text Setting',
type: 'text',
defaultValue: 'Default text'
}),
createMockSetting({
id: 'mixed.number' as any,
name: 'Number Setting',
type: 'number',
defaultValue: 42
}),
createMockSetting({
id: 'mixed.slider' as any,
name: 'Slider Setting',
type: 'slider',
defaultValue: 75,
attrs: { min: 0, max: 100 }
}),
createMockSetting({
id: 'mixed.combo' as any,
name: 'Combo Setting',
type: 'combo',
defaultValue: 'option2',
options: [
{ text: 'Option 1', value: 'option1' },
{ text: 'Option 2', value: 'option2' },
{ text: 'Option 3', value: 'option3' }
]
}),
createMockSetting({
id: 'mixed.color' as any,
name: 'Color Setting',
type: 'color',
defaultValue: '#ff6b35'
})
]
}
]
}
}