mirror of
https://github.com/Comfy-Org/ComfyUI_frontend.git
synced 2026-04-30 03:01:54 +00:00
feat: add Storybook stories for Display components (#9702)
## Summary - Add Storybook stories for `WidgetImageCompare` (Default, WithBatchNavigation, SingleImageFallback, NoImages) - WidgetGalleria and ImagePreview stories are deferred pending PrimeVue removal ## Test plan - [x] `pnpm typecheck` passes - [x] `pnpm lint` passes - [x] Verified all stories render correctly in Storybook Figma ref: https://www.figma.com/design/vALUV83vIdBzEsTJAhQgXq/Comfy-Design-System?node-id=55-1536 ┆Issue is synchronized with this [Notion page](https://www.notion.so/PR-9702-feat-add-Storybook-stories-for-Display-components-31f6d73d365081e781faf3a8735aa3dc) by [Unito](https://www.unito.io) --------- Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,104 @@
|
|||||||
|
import type { Meta, StoryObj } from '@storybook/vue3-vite'
|
||||||
|
import { ref } from 'vue'
|
||||||
|
|
||||||
|
import type { SimplifiedWidget } from '@/types/simplifiedWidget'
|
||||||
|
|
||||||
|
import type { ImageCompareValue } from './WidgetImageCompare.vue'
|
||||||
|
import WidgetImageCompare from './WidgetImageCompare.vue'
|
||||||
|
|
||||||
|
function createSampleImage(label: string, fill: string): string {
|
||||||
|
const svg =
|
||||||
|
`<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512">` +
|
||||||
|
`<rect width="512" height="512" fill="${fill}" />` +
|
||||||
|
`<text x="50%" y="50%" fill="white" font-size="40"` +
|
||||||
|
` text-anchor="middle" dominant-baseline="middle">` +
|
||||||
|
`${label}</text></svg>`
|
||||||
|
return `data:image/svg+xml;charset=utf-8,${encodeURIComponent(svg)}`
|
||||||
|
}
|
||||||
|
|
||||||
|
const SAMPLE_BEFORE = createSampleImage('Before', '#475569')
|
||||||
|
const SAMPLE_AFTER = createSampleImage('After', '#0f766e')
|
||||||
|
|
||||||
|
const meta: Meta<typeof WidgetImageCompare> = {
|
||||||
|
title: 'Components/Display/ImageCompare',
|
||||||
|
component: WidgetImageCompare,
|
||||||
|
tags: ['autodocs'],
|
||||||
|
parameters: { layout: 'centered' },
|
||||||
|
decorators: [
|
||||||
|
(story) => ({
|
||||||
|
components: { story },
|
||||||
|
template: '<div class="w-88 h-80"><story /></div>'
|
||||||
|
})
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
export default meta
|
||||||
|
type Story = StoryObj<typeof meta>
|
||||||
|
|
||||||
|
export const Default: Story = {
|
||||||
|
render: () => ({
|
||||||
|
components: { WidgetImageCompare },
|
||||||
|
setup() {
|
||||||
|
const widget = ref<SimplifiedWidget<ImageCompareValue>>({
|
||||||
|
name: 'compare',
|
||||||
|
type: 'IMAGE_COMPARE',
|
||||||
|
value: {
|
||||||
|
beforeImages: [SAMPLE_BEFORE],
|
||||||
|
afterImages: [SAMPLE_AFTER]
|
||||||
|
}
|
||||||
|
})
|
||||||
|
return { widget }
|
||||||
|
},
|
||||||
|
template: '<WidgetImageCompare :widget="widget" />'
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export const WithBatchNavigation: Story = {
|
||||||
|
render: () => ({
|
||||||
|
components: { WidgetImageCompare },
|
||||||
|
setup() {
|
||||||
|
const widget = ref<SimplifiedWidget<ImageCompareValue>>({
|
||||||
|
name: 'compare',
|
||||||
|
type: 'IMAGE_COMPARE',
|
||||||
|
value: {
|
||||||
|
beforeImages: [SAMPLE_BEFORE, SAMPLE_AFTER],
|
||||||
|
afterImages: [SAMPLE_AFTER, SAMPLE_BEFORE],
|
||||||
|
beforeAlt: 'Before batch',
|
||||||
|
afterAlt: 'After batch'
|
||||||
|
}
|
||||||
|
})
|
||||||
|
return { widget }
|
||||||
|
},
|
||||||
|
template: '<WidgetImageCompare :widget="widget" />'
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export const SingleImageFallback: Story = {
|
||||||
|
render: () => ({
|
||||||
|
components: { WidgetImageCompare },
|
||||||
|
setup() {
|
||||||
|
const widget = ref<SimplifiedWidget<string>>({
|
||||||
|
name: 'compare',
|
||||||
|
type: 'IMAGE_COMPARE',
|
||||||
|
value: SAMPLE_BEFORE
|
||||||
|
})
|
||||||
|
return { widget }
|
||||||
|
},
|
||||||
|
template: '<WidgetImageCompare :widget="widget" />'
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export const NoImages: Story = {
|
||||||
|
render: () => ({
|
||||||
|
components: { WidgetImageCompare },
|
||||||
|
setup() {
|
||||||
|
const widget = ref<SimplifiedWidget<ImageCompareValue>>({
|
||||||
|
name: 'compare',
|
||||||
|
type: 'IMAGE_COMPARE',
|
||||||
|
value: {}
|
||||||
|
})
|
||||||
|
return { widget }
|
||||||
|
},
|
||||||
|
template: '<WidgetImageCompare :widget="widget" />'
|
||||||
|
})
|
||||||
|
}
|
||||||
@@ -58,7 +58,7 @@ describe('WidgetImageCompare Display', () => {
|
|||||||
expect(images[1].attributes('src')).toBe('https://example.com/before.jpg')
|
expect(images[1].attributes('src')).toBe('https://example.com/before.jpg')
|
||||||
|
|
||||||
images.forEach((img) => {
|
images.forEach((img) => {
|
||||||
expect(img.classes()).toContain('object-contain')
|
expect(img.classes()).toContain('object-cover')
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
@@ -290,7 +290,6 @@ describe('WidgetImageCompare Display', () => {
|
|||||||
|
|
||||||
const slider = wrapper.find('[role="presentation"]')
|
const slider = wrapper.find('[role="presentation"]')
|
||||||
expect(slider.exists()).toBe(true)
|
expect(slider.exists()).toBe(true)
|
||||||
expect(slider.classes()).toContain('bg-white')
|
|
||||||
})
|
})
|
||||||
|
|
||||||
it('does not render slider when no images', () => {
|
it('does not render slider when no images', () => {
|
||||||
|
|||||||
@@ -26,14 +26,14 @@
|
|||||||
<div
|
<div
|
||||||
v-if="beforeImage || afterImage"
|
v-if="beforeImage || afterImage"
|
||||||
ref="containerRef"
|
ref="containerRef"
|
||||||
class="relative min-h-0 flex-1"
|
class="relative min-h-0 flex-1 overflow-hidden rounded-lg bg-node-component-surface py-4"
|
||||||
>
|
>
|
||||||
<img
|
<img
|
||||||
v-if="afterImage"
|
v-if="afterImage"
|
||||||
:src="afterImage"
|
:src="afterImage"
|
||||||
:alt="afterAlt"
|
:alt="afterAlt"
|
||||||
draggable="false"
|
draggable="false"
|
||||||
class="size-full object-contain"
|
class="absolute inset-0 size-full object-cover"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<img
|
<img
|
||||||
@@ -41,12 +41,18 @@
|
|||||||
:src="beforeImage"
|
:src="beforeImage"
|
||||||
:alt="beforeAlt"
|
:alt="beforeAlt"
|
||||||
draggable="false"
|
draggable="false"
|
||||||
class="absolute inset-0 size-full object-contain"
|
class="absolute inset-0 size-full object-cover"
|
||||||
:style="{ clipPath: `inset(0 ${100 - sliderPosition}% 0 0)` }"
|
:style="
|
||||||
|
hasCompareImages
|
||||||
|
? { clipPath: `inset(0 ${100 - sliderPosition}% 0 0)` }
|
||||||
|
: undefined
|
||||||
|
"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
<!-- Circular drag handle -->
|
||||||
<div
|
<div
|
||||||
class="pointer-events-none absolute inset-y-0 z-10 w-0.5 bg-white shadow-md"
|
v-if="hasCompareImages"
|
||||||
|
class="pointer-events-none absolute top-1/2 z-10 size-6 -translate-1/2 rounded-full border-2 border-white bg-white/30 shadow-lg backdrop-blur-sm"
|
||||||
:style="{ left: `${sliderPosition}%` }"
|
:style="{ left: `${sliderPosition}%` }"
|
||||||
role="presentation"
|
role="presentation"
|
||||||
/>
|
/>
|
||||||
@@ -142,6 +148,10 @@ const afterImage = computed(() => {
|
|||||||
return value?.afterImages?.[afterIndex.value] ?? ''
|
return value?.afterImages?.[afterIndex.value] ?? ''
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const hasCompareImages = computed(() =>
|
||||||
|
Boolean(beforeImage.value && afterImage.value)
|
||||||
|
)
|
||||||
|
|
||||||
const beforeAlt = computed(() => {
|
const beforeAlt = computed(() => {
|
||||||
const value = props.widget.value
|
const value = props.widget.value
|
||||||
return !isSingleImage(value) && value?.beforeAlt
|
return !isSingleImage(value) && value?.beforeAlt
|
||||||
|
|||||||
Reference in New Issue
Block a user