From 66bf85574f490224a157385378d2c1594671285c Mon Sep 17 00:00:00 2001 From: Gaurrov Date: Fri, 30 Oct 2026 00:05:33 +0530 Subject: [PATCH] fix(catalog-card): give front face its own transform so Firefox hides it when flipped Firefox ignores `backface-visibility: hidden` on the z-indexed CardFront because it has no transform of its own, so after the flip the front face is painted mirrored over CardBack. Give CardFront `rotateY(0deg)` when the card flips, matching the existing FrontFace pattern in the same file. Refs meshery/meshery#22640 Co-Authored-By: Claude Opus 5.5 Signed-off-by: Gaurrov --- src/__testing__/CustomCatalogCard.test.tsx | 55 ++++++++++++++++++++++ src/custom/CustomCatalog/style.tsx | 3 ++ 2 files changed, 58 insertions(+) create mode 100644 src/__testing__/CustomCatalogCard.test.tsx diff --git a/src/__testing__/CustomCatalogCard.test.tsx b/src/__testing__/CustomCatalogCard.test.tsx new file mode 100644 index 000000000..ce1d2801c --- /dev/null +++ b/src/__testing__/CustomCatalogCard.test.tsx @@ -0,0 +1,55 @@ +import { render } from '@testing-library/react'; +import React from 'react'; +import { CustomCatalogCard } from '../custom/CustomCatalog'; +import type { Pattern } from '../custom/CustomCatalog/CustomCard'; +import { SistentThemeProvider } from '../theme'; + +// CustomCatalogCard -> CustomTooltip -> Markdown -> react-markdown (ESM-only) is +// excluded by jest's transformIgnorePatterns allowlist, so those leaf modules are stubbed. +jest.mock('react-markdown', () => ({ + __esModule: true, + default: ({ children }: { children: React.ReactNode }) =>
{children}
+})); +jest.mock('remark-gfm', () => ({ __esModule: true, default: () => {} })); +jest.mock('rehype-raw', () => ({ __esModule: true, default: () => {} })); + +const renderWithTheme = (ui: React.ReactElement) => + render({ui}); + +const pattern = { + id: 'design-1', + userId: 'user-1', + patternFile: 'name: API Architecture', + name: 'API Architecture', + visibility: 'public', + avatarUrl: '', + user: { firstName: 'Ada', lastName: 'Lovelace' }, + catalogData: { contentClass: 'community', compatibility: [] } +} as unknown as Pattern; + +// The flipping element is `.innerCard`; its first child is the front face. +const getFrontFace = (container: HTMLElement) => + container.querySelector('.innerCard')?.firstElementChild as HTMLElement; + +describe('CustomCatalogCard', () => { + it('gives the front face its own 3D transform when the card flips', () => { + // Regression for meshery/meshery#22640: Firefox ignores + // `backface-visibility: hidden` on a z-indexed face that has no transform + // of its own, painting the front face mirrored over the back on hover. + const { container } = renderWithTheme( + + ); + + const front = getComputedStyle(getFrontFace(container)); + expect(front.backfaceVisibility).toBe('hidden'); + expect(front.transform).toBe('rotateY(0deg)'); + }); + + it('leaves the front face untransformed when the card does not flip', () => { + const { container } = renderWithTheme( + + ); + + expect(getComputedStyle(getFrontFace(container)).transform).toBe(''); + }); +}); diff --git a/src/custom/CustomCatalog/style.tsx b/src/custom/CustomCatalog/style.tsx index 45e3b329b..989de2f13 100644 --- a/src/custom/CustomCatalog/style.tsx +++ b/src/custom/CustomCatalog/style.tsx @@ -377,6 +377,9 @@ export const CardFront = styled('div')(({ shouldFlip, isDeta zIndex: 1, ...(shouldFlip && { position: 'absolute', + // Firefox ignores backface-visibility on a z-indexed face without its own + // transform, painting the front mirrored over CardBack (meshery/meshery#22640). + transform: 'rotateY(0deg)', boxShadow, background }),