mirror of
https://github.com/Comfy-Org/ComfyUI_frontend.git
synced 2026-03-13 09:00:16 +00:00
feat: replace PrimeVue Galleria/Skeleton with custom DisplayCarousel and ImagePreview (#9712)
## Summary - Replace `primevue/galleria` with custom `DisplayCarousel` component featuring Single (carousel) and Grid display modes - Hover action buttons (mask, download, remove) appear on image hover/focus - Thumbnail strip with prev/next navigation; arrows at edges, thumbnails centered - Grid mode uses fixed 56px image tiles matching Figma spec - Replace `primevue/skeleton` and `useToast()` in `ImagePreview` with `Skeleton.vue` and `useToastStore()` - Rename `WidgetGalleria` → `DisplayCarousel` across registry, stories, and tests - Add Storybook stories for both `DisplayCarousel` and `ImagePreview` - Retain `WidgetGalleriaOriginal` with its own story for side-by-side comparison ## Test plan - [x] Unit tests pass (30 DisplayCarousel + 21 ImagePreview) - [x] `pnpm typecheck` clean - [x] `pnpm lint` clean - [x] `pnpm knip` clean - [x] Visual verification via Storybook: hover controls, nav, grid mode, single/grid toggle - [x] Manual Storybook check: Components/Display/DisplayCarousel, Components/Display/ImagePreview ## screenshot <img width="604" height="642" alt="스크린샷 2026-03-12 오후 2 01 51" src="https://github.com/user-attachments/assets/94df3070-9910-470b-a8f5-5507433ef6e6" /> <img width="609" height="651" alt="스크린샷 2026-03-12 오후 2 04 47" src="https://github.com/user-attachments/assets/3d9884b4-f1bd-4ef5-957a-c7cf7fdc04d8" /> <img width="729" height="681" alt="스크린샷 2026-03-12 오후 2 04 49" src="https://github.com/user-attachments/assets/715f9367-17a3-4d7b-b81f-a7cd6bd446bf" /> <img width="534" height="460" alt="스크린샷 2026-03-12 오후 2 05 39" src="https://github.com/user-attachments/assets/b810eee2-55cb-4dbd-aaca-6331527d13ca" /> 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -101,7 +101,7 @@ describe('ImagePreview', () => {
|
||||
it('shows navigation dots for multiple images', () => {
|
||||
const wrapper = mountImagePreview()
|
||||
|
||||
const navigationDots = wrapper.findAll('.w-2.h-2.rounded-full')
|
||||
const navigationDots = wrapper.findAll('[aria-label*="View image"]')
|
||||
expect(navigationDots).toHaveLength(2)
|
||||
})
|
||||
|
||||
@@ -110,7 +110,7 @@ describe('ImagePreview', () => {
|
||||
imageUrls: [defaultProps.imageUrls[0]]
|
||||
})
|
||||
|
||||
const navigationDots = wrapper.findAll('.w-2.h-2.rounded-full')
|
||||
const navigationDots = wrapper.findAll('[aria-label*="View image"]')
|
||||
expect(navigationDots).toHaveLength(0)
|
||||
})
|
||||
|
||||
@@ -249,7 +249,7 @@ describe('ImagePreview', () => {
|
||||
)
|
||||
|
||||
// Click second navigation dot
|
||||
const navigationDots = wrapper.findAll('.w-2.h-2.rounded-full')
|
||||
const navigationDots = wrapper.findAll('[aria-label*="View image"]')
|
||||
await navigationDots[1].trigger('click')
|
||||
await nextTick()
|
||||
|
||||
@@ -259,22 +259,22 @@ describe('ImagePreview', () => {
|
||||
expect(imgElement.attributes('src')).toBe(defaultProps.imageUrls[1])
|
||||
})
|
||||
|
||||
it('applies correct classes to navigation dots based on current image', async () => {
|
||||
it('marks active navigation dot with aria-current', async () => {
|
||||
const wrapper = mountImagePreview()
|
||||
|
||||
const navigationDots = wrapper.findAll('.w-2.h-2.rounded-full')
|
||||
const navigationDots = wrapper.findAll('[aria-label*="View image"]')
|
||||
|
||||
// First dot should be active (has bg-white class)
|
||||
expect(navigationDots[0].classes()).toContain('bg-base-foreground')
|
||||
expect(navigationDots[1].classes()).toContain('bg-base-foreground/50')
|
||||
// First dot should be active
|
||||
expect(navigationDots[0].attributes('aria-current')).toBe('true')
|
||||
expect(navigationDots[1].attributes('aria-current')).toBeUndefined()
|
||||
|
||||
// Switch to second image
|
||||
await navigationDots[1].trigger('click')
|
||||
await nextTick()
|
||||
|
||||
// Second dot should now be active
|
||||
expect(navigationDots[0].classes()).toContain('bg-base-foreground/50')
|
||||
expect(navigationDots[1].classes()).toContain('bg-base-foreground')
|
||||
expect(navigationDots[0].attributes('aria-current')).toBeUndefined()
|
||||
expect(navigationDots[1].attributes('aria-current')).toBe('true')
|
||||
})
|
||||
|
||||
it('loads image without errors', async () => {
|
||||
@@ -301,7 +301,7 @@ describe('ImagePreview', () => {
|
||||
expect(wrapper.find('img').attributes('alt')).toBe('Node output 1')
|
||||
|
||||
// Switch to second image
|
||||
const navigationDots = wrapper.findAll('.w-2.h-2.rounded-full')
|
||||
const navigationDots = wrapper.findAll('[aria-label*="View image"]')
|
||||
await navigationDots[1].trigger('click')
|
||||
await nextTick()
|
||||
|
||||
@@ -326,7 +326,7 @@ describe('ImagePreview', () => {
|
||||
expect(wrapper.find('[aria-busy="true"]').exists()).toBe(false)
|
||||
|
||||
// Click second navigation dot to cycle
|
||||
const dots = wrapper.findAll('.w-2.h-2.rounded-full')
|
||||
const dots = wrapper.findAll('[aria-label*="View image"]')
|
||||
await dots[1].trigger('click')
|
||||
await nextTick()
|
||||
|
||||
|
||||
Reference in New Issue
Block a user