import { useSortable } from "@dnd-kit/sortable" import { CSS } from "@dnd-kit/utilities" import { DotsSix, Eye, EyeSlash } from "@medusajs/icons" import { IconButton, clx } from "@medusajs/ui" import { ReactNode } from "react" import { useTranslation } from "react-i18next" import type { LayoutControlSize } from "./types" /** * An entry's rendered content plus a placeholder that appears (via the * `peer`/`:empty` pair) only when that content produces no DOM — e.g. a section * that exists solely while an edit is pending. Keeps such an entry a visible, * labeled card rather than collapsing to a 0-height row. Shared by the in-place * `SortableEntry` and the `DragOverlay` ghost so the two never drift. * * `empty:hidden` collapses the content box when it renders nothing so it stops * reserving height — otherwise a layout that stretches the entry (e.g. a grid * equalizing row heights) would size the empty content to the full cell and * stack the placeholder below it, showing two boxes for one empty entry. */ export function EntryContent({ children, className, placeholderClassName, }: { children: ReactNode className?: string placeholderClassName?: string }) { const { t } = useTranslation() return ( <>
{children}
) } type SortableEntryProps = { widgetId: string order: number hidden: boolean onToggleHidden: () => void children: ReactNode controlSize?: LayoutControlSize } export function SortableEntry({ widgetId, order, hidden, onToggleHidden, children, controlSize = "default", }: SortableEntryProps) { const { t } = useTranslation() const { attributes, listeners, setNodeRef, transform, transition, isDragging, } = useSortable({ id: widgetId }) // `CSS.Translate.toString` applies translate only, dropping the strategy's // scaleX/scaleY (which would stretch the dragged item to match the swapped // neighbor's box). The actual dragged ghost is rendered separately via // . const style = { transform: CSS.Translate.toString(transform), transition, } const xsmall = controlSize === "xsmall" const small = controlSize === "small" const showLabel = controlSize === "default" return (
*]` because the `display: contents` // SortableContext wrapper hides its children from the `>` combinator. "ring-ui-border-base relative min-w-0 rounded-lg ring-1 transition-opacity", // Hidden entries are clearly de-emphasized during edit mode so the // user can tell at a glance which ones won't render at idle. hidden && "opacity-30 grayscale", // Hide the original while it's being dragged — the DragOverlay shows // the moving copy. Visibility (not display:none) keeps the layout box // in place so neighbors can shift against it. isDragging && "invisible" )} > {/* `flex flex-col` makes the rendered content a flex item again (as it is in idle mode), so it re-establishes its own block formatting context and contains any trailing margins of its children — without this, a child's bottom margin escapes through these plain wrapper divs and inflates this box past the content's background. `h-full` + `[&>*]:h-full` let the content fill the wrapper when a layout stretches the entry to a fixed height (e.g. a grid section equalizing row heights). When the wrapper height is auto (list sections) these resolve to the content height, so they're inert. */} {children} {/* Overlay rendered after children so it stacks above them by DOM order */}
{showLabel && ( {widgetId} ({order}) )} {hidden ? : }
) }