import { useDroppable } from "@dnd-kit/core" import { SortableContext, horizontalListSortingStrategy, rectSortingStrategy, verticalListSortingStrategy, } from "@dnd-kit/sortable" import { Text, clx } from "@medusajs/ui" import { ReactNode } from "react" import { useTranslation } from "react-i18next" import type { LayoutSection } from "./types" import { getSectionTailId } from "./entries" type SectionTailProps = { sectionId: string className?: string } /** * A persistent drop area at the end of a section. Without it the only place to * drop something "at the end" is the few pixels past the last entry, with no * visual cue that a cross-section move landed. This gives a generous, clearly * marked target that highlights while hovered. */ function SectionTail({ sectionId, className }: SectionTailProps) { const { setNodeRef, isOver } = useDroppable({ id: getSectionTailId(sectionId), }) const { t } = useTranslation() return (
{t("layout.dropToSectionEnd")}
) } type SectionDropzoneProps = { section: LayoutSection items: string[] children: ReactNode } /** * Renders the droppable/sortable area for a single layout section in edit * mode. The visual treatment branches on `section.ordering`: * * - `"list"`: we own a flex-column container (matches idle-mode stacking), * sorted with `verticalListSortingStrategy`. * - `"grid"`: we stay out of the way with `display: contents` so the * `SortableEntry` wrappers become direct children of whatever grid (or * other) container the `LayoutComponent` defines for this section, * preserving its column count/gaps/breakpoints. Sorted with * `rectSortingStrategy`, which computes swaps from real 2D rects instead * of assuming a single vertical axis. When the section is empty there's no * element to anchor the droppable to, so we render a full-width placeholder * box instead. * - `"horizontal-stretched"`: owns a single, non-wrapping flex row (like * `"list"` owns a column), sorted with `horizontalListSortingStrategy`. * `[&>*]:flex-1` gives each entry an equal share of the width and * `[&>*]:min-w-0` lets them shrink so wide content truncates instead of * forcing unequal widths. Owning the container (rather than delegating via * `display: contents` like `"grid"`) is deliberate: the `[&>*]` utilities are * applied by the entries' direct parent, so they actually reach them in edit * mode. No `SectionTail` — a width-filling row has no end gap; reordering past * the last item covers end placement. * - `"horizontal-list"`: like `"horizontal-stretched"` but entries keep their * natural size and are vertically centered (`items-center`) * * `"list"` and `"grid"` render a `SectionTail` last as a generous * end-of-section drop target. */ export function SectionDropzone({ section, items, children, }: SectionDropzoneProps) { const { setNodeRef, isOver } = useDroppable({ id: section.id }) if (section.ordering === "grid") { return ( {items.length === 0 ? (
) : ( // `display: contents` keeps the SortableEntry wrappers as direct // children of the author's grid, so this node has no box of its own. // Drops onto the grid are covered by the entries and the SectionTail.
{children}
)} ) } if (section.ordering === "horizontal-stretched") { return (
*]:min-w-0 [&>*]:flex-1", isOver && items.length === 0 && "bg-ui-bg-highlight" )} > {children}
) } if (section.ordering === "horizontal-list") { return (
{children}
) } return (
{children}
) }