Skip to content

Commit caaa952

Browse files
snomiaoclaude
andcommitted
feat(storybook): add ComfyNode admin components stories
- Add ComfyNodeEditModal.stories.tsx for modal component testing - Add ComfyNodePolicyBadge.stories.tsx for policy badge variants - Add ComfyNodeTable.stories.tsx for admin table component - Add ComfyNodesManage.stories.tsx for full page story - Export ComfyNodeEditModal component for reusability 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
1 parent 53e44c5 commit caaa952

File tree

5 files changed

+685
-3
lines changed

5 files changed

+685
-3
lines changed

pages/admin/comfy-nodes.tsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ interface FormData {
4949
policy: ComfyNodePolicy
5050
}
5151

52-
function ComfyNodeEditModal({
52+
export function ComfyNodeEditModal({
5353
isOpen,
5454
onClose,
5555
comfyNode,
@@ -93,8 +93,8 @@ function ComfyNodeEditModal({
9393
return_types: data.return_types,
9494
output_is_list: data.output_is_list
9595
? data.output_is_list
96-
.split(',')
97-
.map((s) => s.trim() === 'true')
96+
.split(',')
97+
.map((s) => s.trim() === 'true')
9898
: undefined,
9999
deprecated: data.deprecated,
100100
experimental: data.experimental,
Lines changed: 129 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,129 @@
1+
import { Meta, StoryObj } from '@storybook/nextjs-vite'
2+
import { useState } from 'react'
3+
import { Button } from 'flowbite-react'
4+
import { ComfyNode, ComfyNodePolicy } from '@/src/api/generated'
5+
6+
// Import the modal component directly from the page file
7+
import { ComfyNodeEditModal } from '@/pages/admin/comfy-nodes'
8+
9+
// Mock ComfyNode data
10+
const mockComfyNode: ComfyNode = {
11+
comfy_node_name: 'TestNode',
12+
category: 'Image Processing',
13+
description: 'A test node for image processing operations',
14+
function: 'process_image',
15+
input_types: 'IMAGE, CONDITIONING',
16+
return_names: 'image, mask',
17+
return_types: 'IMAGE, MASK',
18+
output_is_list: [false, true],
19+
deprecated: false,
20+
experimental: false,
21+
policy: ComfyNodePolicy.ComfyNodePolicyActive,
22+
}
23+
24+
const mockDeprecatedNode: ComfyNode = {
25+
...mockComfyNode,
26+
comfy_node_name: 'DeprecatedNode',
27+
category: 'Legacy',
28+
description: 'An old node that should not be used anymore',
29+
deprecated: true,
30+
policy: ComfyNodePolicy.ComfyNodePolicyBanned,
31+
}
32+
33+
const mockExperimentalNode: ComfyNode = {
34+
...mockComfyNode,
35+
comfy_node_name: 'ExperimentalNode',
36+
category: 'Research',
37+
description: 'An experimental node for testing new features',
38+
experimental: true,
39+
policy: ComfyNodePolicy.ComfyNodePolicyLocalOnly,
40+
}
41+
42+
// Wrapper component to handle modal state
43+
const ModalWrapper = ({
44+
comfyNode,
45+
nodeId = 'test-node',
46+
version = '1.0.0',
47+
}) => {
48+
const [isOpen, setIsOpen] = useState(false)
49+
50+
return (
51+
<div>
52+
<Button onClick={() => setIsOpen(true)}>Open Edit Modal</Button>
53+
<ComfyNodeEditModal
54+
isOpen={isOpen}
55+
onClose={() => setIsOpen(false)}
56+
comfyNode={comfyNode}
57+
nodeId={nodeId}
58+
version={version}
59+
onSuccess={() => {
60+
console.log('Edit successful!')
61+
setIsOpen(false)
62+
}}
63+
/>
64+
</div>
65+
)
66+
}
67+
68+
const meta: Meta<typeof ModalWrapper> = {
69+
title: 'Components/Admin/ComfyNodeEditModal',
70+
component: ModalWrapper,
71+
parameters: {
72+
layout: 'centered',
73+
},
74+
tags: ['autodocs'],
75+
decorators: [
76+
(Story) => (
77+
<div className="dark min-h-screen bg-gray-900 p-4">
78+
<Story />
79+
</div>
80+
),
81+
],
82+
}
83+
84+
export default meta
85+
type Story = StoryObj<typeof ModalWrapper>
86+
87+
export const Default: Story = {
88+
args: {
89+
comfyNode: mockComfyNode,
90+
nodeId: 'test-node',
91+
version: '1.0.0',
92+
},
93+
}
94+
95+
export const DeprecatedNode: Story = {
96+
args: {
97+
comfyNode: mockDeprecatedNode,
98+
nodeId: 'deprecated-node',
99+
version: '0.9.0',
100+
},
101+
}
102+
103+
export const ExperimentalNode: Story = {
104+
args: {
105+
comfyNode: mockExperimentalNode,
106+
nodeId: 'experimental-node',
107+
version: '2.0.0-alpha',
108+
},
109+
}
110+
111+
export const MinimalNode: Story = {
112+
args: {
113+
comfyNode: {
114+
comfy_node_name: 'MinimalNode',
115+
category: '',
116+
description: '',
117+
function: '',
118+
input_types: '',
119+
return_names: '',
120+
return_types: '',
121+
output_is_list: [],
122+
deprecated: false,
123+
experimental: false,
124+
policy: ComfyNodePolicy.ComfyNodePolicyActive,
125+
},
126+
nodeId: 'minimal-node',
127+
version: '1.0.0',
128+
},
129+
}
Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
import { Meta, StoryObj } from '@storybook/nextjs-vite'
2+
import { Badge } from 'flowbite-react'
3+
import { ComfyNodePolicy } from '@/src/api/generated'
4+
5+
// Component that mimics the policy badge logic from the admin page
6+
const ComfyNodePolicyBadge = ({ policy }: { policy?: ComfyNodePolicy }) => {
7+
const getPolicyBadgeColor = (policy: ComfyNodePolicy) => {
8+
switch (policy) {
9+
case ComfyNodePolicy.ComfyNodePolicyActive:
10+
return 'success'
11+
case ComfyNodePolicy.ComfyNodePolicyBanned:
12+
return 'failure'
13+
case ComfyNodePolicy.ComfyNodePolicyLocalOnly:
14+
return 'warning'
15+
default:
16+
return 'gray'
17+
}
18+
}
19+
20+
const getPolicyLabel = (policy: ComfyNodePolicy) => {
21+
switch (policy) {
22+
case ComfyNodePolicy.ComfyNodePolicyActive:
23+
return 'Active'
24+
case ComfyNodePolicy.ComfyNodePolicyBanned:
25+
return 'Banned'
26+
case ComfyNodePolicy.ComfyNodePolicyLocalOnly:
27+
return 'Local Only'
28+
default:
29+
return 'Unknown'
30+
}
31+
}
32+
33+
return (
34+
<Badge color={policy ? getPolicyBadgeColor(policy) : 'gray'} size="sm">
35+
{policy ? getPolicyLabel(policy) : 'No Policy'}
36+
</Badge>
37+
)
38+
}
39+
40+
const meta: Meta<typeof ComfyNodePolicyBadge> = {
41+
title: 'Components/Admin/ComfyNodePolicyBadge',
42+
component: ComfyNodePolicyBadge,
43+
parameters: {
44+
layout: 'centered',
45+
},
46+
tags: ['autodocs'],
47+
decorators: [
48+
(Story) => (
49+
<div className="dark bg-gray-900 p-4">
50+
<Story />
51+
</div>
52+
),
53+
],
54+
}
55+
56+
export default meta
57+
type Story = StoryObj<typeof ComfyNodePolicyBadge>
58+
59+
export const Active: Story = {
60+
args: {
61+
policy: ComfyNodePolicy.ComfyNodePolicyActive,
62+
},
63+
}
64+
65+
export const Banned: Story = {
66+
args: {
67+
policy: ComfyNodePolicy.ComfyNodePolicyBanned,
68+
},
69+
}
70+
71+
export const LocalOnly: Story = {
72+
args: {
73+
policy: ComfyNodePolicy.ComfyNodePolicyLocalOnly,
74+
},
75+
}
76+
77+
export const NoPolicy: Story = {
78+
args: {
79+
policy: undefined,
80+
},
81+
}
82+
83+
// Show all variants together
84+
export const AllVariants: Story = {
85+
render: () => (
86+
<div className="flex gap-2 flex-wrap">
87+
<ComfyNodePolicyBadge
88+
policy={ComfyNodePolicy.ComfyNodePolicyActive}
89+
/>
90+
<ComfyNodePolicyBadge
91+
policy={ComfyNodePolicy.ComfyNodePolicyBanned}
92+
/>
93+
<ComfyNodePolicyBadge
94+
policy={ComfyNodePolicy.ComfyNodePolicyLocalOnly}
95+
/>
96+
<ComfyNodePolicyBadge policy={undefined} />
97+
</div>
98+
),
99+
}

0 commit comments

Comments
 (0)