Skip to content
Open
Show file tree
Hide file tree
Changes from all 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
34 changes: 26 additions & 8 deletions src/components/puck/blocks/grid.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,13 @@
import type { ComponentConfig, Slot } from "@puckeditor/core";
"use client";

import { createUsePuck, type ComponentConfig, Slot } from "@puckeditor/core";
import { defineProps, responsive, field } from "@/lib/puck/define-props";
import type { ResponsiveValue } from "@/lib/puck/responsive";
import { columnCount, gap, gridRows, type ColumnCount, type Spacing, type GridRows } from "@/lib/puck/tokens";
import { getGridClassName } from "@/lib/puck/layout";
import { cn } from "@/lib/utils";
import { getGridCapacity, getGridClassName } from "@/lib/puck/layout";

const usePuck = createUsePuck();

type GridProps = {
content: Slot;
Expand All @@ -22,7 +27,11 @@
label: "Grid",
inline: true,
...props,
render: ({ content: Content, columns, rows: r, gap, puck }) => {
render: ({ content: Content, columns, rows: r, gap, puck, id }) => {
const viewportWidth = usePuck((state) => state.appState.ui.viewports.current.width);

Check failure on line 31 in src/components/puck/blocks/grid.tsx

View workflow job for this annotation

GitHub Actions / build

React Hook "usePuck" is called in function "render" that is neither a React function component nor a custom React Hook function. React component names must start with an uppercase letter. React Hook names must start with the word "use"
const capacity = getGridCapacity(columns, r, viewportWidth);
const overflowClassName = `grid-overflow-${id}`;

if (!Content) {
return (
<div
Expand All @@ -37,12 +46,21 @@
);
}

const className = cn(getGridClassName({ columns, rows: r, gap }), overflowClassName);
const overflowStyle = capacity !== null
? `.${overflowClassName} > [data-puck-component]:nth-child(n + ${capacity + 1}) { display: none; }`
: null;

return (
<Content
ref={puck.dragRef}
className={getGridClassName({ columns, rows: r, gap })}
minEmptyHeight="200px"
/>
<div className="w-full">
{overflowStyle && <style>{overflowStyle}</style>}
<Content
ref={puck.dragRef}
className={className}
minEmptyHeight="200px"
/>
{/* Hidden items are visually clamped via injected CSS when capacity is set. */}
</div>
);
},
};
19 changes: 19 additions & 0 deletions src/lib/puck/layout.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { describe, expect, it } from "vitest";
import {
getContainerSlotClassName,
getContainerSurfaceClassName,
getGridCapacity,
getGridClassName,
getMaxCols,
} from "./layout";
Expand Down Expand Up @@ -45,4 +46,22 @@ describe("layout helpers", () => {
it("returns the largest configured breakpoint column count", () => {
expect(getMaxCols({ base: "1", md: "3", lg: "2" })).toBe(3);
});

it("resolves grid capacity from the active responsive values", () => {
expect(
getGridCapacity(
{ base: "1", md: "3" },
{ base: "auto", md: "2" },
500,
),
).toBeNull();

expect(
getGridCapacity(
{ base: "1", md: "3" },
{ base: "auto", md: "2" },
1280,
),
).toBe(6);
});
});
43 changes: 43 additions & 0 deletions src/lib/puck/layout.ts
Original file line number Diff line number Diff line change
Expand Up @@ -95,3 +95,46 @@ export function getGridClassName({
export function getMaxCols(columns: ResponsiveValue<ColumnCount>): number {
return Math.max(...Object.values(columns).map(Number));
}

const responsiveViewportWidths = {
md: 768,
lg: 1024,
} as const;

function resolveResponsiveValue<T>(
value: ResponsiveValue<T>,
viewportWidth: number | "100%",
): T {
const orderedBreakpoints = viewportWidth === "100%"
? ["lg", "md", "base"] as const
: viewportWidth >= responsiveViewportWidths.lg
? ["lg", "md", "base"] as const
: viewportWidth >= responsiveViewportWidths.md
? ["md", "base"] as const
: ["base"] as const;

for (const breakpoint of orderedBreakpoints) {
const breakpointValue = value[breakpoint];

if (breakpointValue !== undefined) {
return breakpointValue;
}
}

return value.base;
}

export function getGridCapacity(
columns: ResponsiveValue<ColumnCount>,
rows: ResponsiveValue<GridRows>,
viewportWidth: number | "100%",
): number | null {
const resolvedRows = resolveResponsiveValue(rows, viewportWidth);

if (resolvedRows === "auto") {
return null;
}

const resolvedColumns = Number(resolveResponsiveValue(columns, viewportWidth));
return resolvedColumns * Number(resolvedRows);
}
Loading