Skip to content
Draft
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
45 changes: 45 additions & 0 deletions packages/react/cypress/component/VisualWrapper.cy.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,51 @@ it("supports respponsive aspect function", () => {
cy.get(".wrapper").hasDimensions(400, 400);
});

it("generates CSS classes using useId() for responsive aspects", () => {
cy.mount(
<VisualWrapper
{...sharedProps}
image={{
landscape: {
aspect: 2,
},
portrait: {
aspect: 1,
},
}}
sourceMedia={["(orientation: landscape)", "(orientation: portrait)"]}
aspect={({ image, media }) => {
return media?.includes("landscape")
? image.landscape.aspect
: image.portrait.aspect;
}}
/>,
);

// Check that CSS classes are generated using useId format (not the old rv-* format)
cy.get(".wrapper").should(($wrapper) => {
const className = $wrapper.attr("class") || "";
const classes = className.split(" ");

// Find classes that look like useId generated classes (should contain React's ID format)
const useIdClasses = classes.filter(cls =>
cls.includes(":") || (cls.startsWith("r") && cls.includes("-") && !cls.startsWith("rv-"))
);

// Should have generated classes for responsive aspects
expect(useIdClasses.length).to.be.greaterThan(0);

// Should NOT have the old bespoke format classes starting with rv-
const oldFormatClasses = classes.filter(cls =>
cls.startsWith("rv-") && (cls.includes("landscape") || cls.includes("portrait"))
);
expect(oldFormatClasses.length).to.equal(0);
});

// Verify that a style tag was created with the responsive CSS
cy.get("style").should("exist").and("contain", "aspect-ratio");
});

it("supports children", () => {
cy.mount(
<VisualWrapper {...sharedProps}>
Expand Down
18 changes: 10 additions & 8 deletions packages/react/src/VisualWrapper.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import type { CSSProperties, ReactNode } from "react";
import { useId } from "react";
import { fillStyles, cx } from "./lib/styles";
import { isNumeric } from "./lib/values";
import type { VisualWrapperProps } from "./types/visualWrapperTypes";
Expand All @@ -10,6 +11,7 @@ type MakeResponsiveAspectsProps = Pick<
> & {
sourceMedia: Required<VisualWrapperProps>["sourceMedia"];
aspectCalculator: AspectCalculator;
baseId: string;
};

// Wraps media elements and applys layout and other functionality
Expand All @@ -26,6 +28,9 @@ export default function VisualWrapper({
style,
dataAttributes,
}: VisualWrapperProps): ReactNode {
// Generate unique ID for CSS classes when needed
const baseId = useId();
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you changes instances of baseId to cssId for more clarity on what this id is used for

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Changed all instances of baseId to cssId for better clarity on what this ID is used for. Commit 6967131.


// If aspect is a function, invoke it to determine the aspect ratio
let aspectRatio, aspectStyleTag, aspectClasses;
if (typeof aspect == "function" && sourceMedia && sourceMedia.length) {
Expand All @@ -34,6 +39,7 @@ export default function VisualWrapper({
sourceMedia,
image,
video,
baseId,
}));
} else aspectRatio = aspect;

Expand Down Expand Up @@ -68,23 +74,19 @@ function makeResponsiveAspects({
sourceMedia,
image,
video,
baseId,
}: MakeResponsiveAspectsProps): {
aspectClasses: string;
aspectStyleTag: ReactNode;
} {
// Make CSS classes and related rules that are specific to the query and
// aspect value.
const styles = sourceMedia.map((mediaQuery) => {
const styles = sourceMedia.map((mediaQuery, index) => {
// Calculate the aspect for this query state
const aspect = aspectCalculator({ media: mediaQuery, image, video });

// Make a CSS class name from the media query string
const mediaClass = mediaQuery.replace(/[^\w]/gi, "-"); // Replace special chars with "-"
const aspectClass = aspect
.toFixed(3)
.replace(/\./gi, "_") // Replace decimals
.replace(/_?0*$/, ""); // Remove trailing 0s
const cssClass = `rv-${mediaClass}-${aspectClass}`.replace(/\-{2,}/g, "-"); // Reduce multiples of `-`
// Make a CSS class name using the base ID and index
const cssClass = `${baseId.replace(/:/g, "")}-${index}`;

// Make the CSS rule
const cssRule = `@media ${mediaQuery} {
Expand Down