| 1 | "use client"; |
| 2 | |
| 3 | import { SlateElement, type SlateElementProps } from "platejs/static"; |
| 4 | import { useId } from "react"; |
| 5 | |
| 6 | import { cn } from "@/lib/utils"; |
| 7 | import { columnSizeVariant, getDefaultColumnSize } from "../../utils"; |
| 8 | import { |
| 9 | ALTERNATING_BOX_COLUMN_GAP_PX, |
| 10 | ALTERNATING_BOX_MIN_ROW_HEIGHT_PX, |
| 11 | ALTERNATING_BOX_ROW_GAP_PX, |
| 12 | getAlternatingBoxPlacementCss, |
| 13 | useAlternatingBoxFitScale, |
| 14 | } from "../alternating-box-layout"; |
| 15 | import { getDiagramFitFrameStyle } from "../diagram-fit"; |
| 16 | import { getStaticDiagramJustifyClass } from "./diagram-static-utils"; |
| 17 | |
| 18 | export default function BoxGroupStatic(props: SlateElementProps) { |
| 19 | const alternatingLayoutId = useId(); |
| 20 | const { columnSize: explicitColumnSize, boxType = "solid" } = |
| 21 | props.element as { |
| 22 | columnSize?: "sm" | "md" | "lg" | "xl"; |
| 23 | boxType?: string; |
| 24 | alignment?: "left" | "center" | "right"; |
| 25 | }; |
| 26 | const element = props.element as { |
| 27 | alignment?: "left" | "center" | "right"; |
| 28 | children?: unknown[]; |
| 29 | orientation?: "horizontal" | "vertical"; |
| 30 | }; |
| 31 | const alignment = element.alignment ?? "center"; |
| 32 | const columnSize = |
| 33 | explicitColumnSize ?? getDefaultColumnSize(element.children?.length ?? 0); |
| 34 | const orientation = element.orientation ?? "horizontal"; |
| 35 | const isAlternating = boxType === "alternating"; |
| 36 | const alternatingItemCount = Math.max(element.children?.length ?? 0, 1); |
| 37 | const alternatingPlacementCss = getAlternatingBoxPlacementCss( |
| 38 | alternatingLayoutId, |
| 39 | alternatingItemCount, |
| 40 | orientation, |
| 41 | ); |
| 42 | const { containerRef, fitStyle, frameStyle, layoutRef } = |
| 43 | useAlternatingBoxFitScale<HTMLDivElement>( |
| 44 | alternatingItemCount, |
| 45 | orientation, |
| 46 | ); |
| 47 | |
| 48 | if (isAlternating) { |
| 49 | return ( |
| 50 | <SlateElement {...props} className="mb-4"> |
| 51 | <div |
| 52 | ref={containerRef} |
| 53 | className={cn( |
| 54 | "flex w-full overflow-visible", |
| 55 | getStaticDiagramJustifyClass(alignment), |
| 56 | )} |
| 57 | > |
| 58 | <div |
| 59 | style={{ |
| 60 | ...frameStyle, |
| 61 | ...getDiagramFitFrameStyle(alignment), |
| 62 | }} |
| 63 | > |
| 64 | <style>{alternatingPlacementCss}</style> |
| 65 | <div ref={layoutRef} className="relative" style={fitStyle}> |
| 66 | <div |
| 67 | aria-hidden="true" |
| 68 | className="pointer-events-none absolute rounded-full" |
| 69 | data-decor="true" |
| 70 | style={{ |
| 71 | background: |
| 72 | "color-mix(in oklch, var(--presentation-smart-layout, var(--presentation-primary)) 72%, transparent)", |
| 73 | boxShadow: |
| 74 | "0 0 0 1px color-mix(in oklch, var(--presentation-background) 65%, transparent)", |
| 75 | ...(orientation === "vertical" |
| 76 | ? { |
| 77 | bottom: 0, |
| 78 | left: "50%", |
| 79 | top: 0, |
| 80 | transform: "translateX(-50%)", |
| 81 | width: 2, |
| 82 | zIndex: 20, |
| 83 | } |
| 84 | : { |
| 85 | height: 2, |
| 86 | left: 0, |
| 87 | right: 0, |
| 88 | top: "50%", |
| 89 | transform: "translateY(-50%)", |
| 90 | zIndex: 20, |
| 91 | }), |
| 92 | }} |
| 93 | /> |
| 94 | <div |
| 95 | className="relative z-10 grid items-stretch *:min-w-0" |
| 96 | data-alternating-box-layout={alternatingLayoutId} |
| 97 | style={{ |
| 98 | columnGap: ALTERNATING_BOX_COLUMN_GAP_PX, |
| 99 | gridTemplateColumns: |
| 100 | orientation === "vertical" |
| 101 | ? "repeat(2, minmax(0, 1fr))" |
| 102 | : `repeat(${alternatingItemCount}, minmax(0, 1fr))`, |
| 103 | gridTemplateRows: |
| 104 | orientation === "vertical" |
| 105 | ? `repeat(${alternatingItemCount}, minmax(${ALTERNATING_BOX_MIN_ROW_HEIGHT_PX}px, auto))` |
| 106 | : `repeat(2, minmax(${ALTERNATING_BOX_MIN_ROW_HEIGHT_PX}px, 1fr))`, |
| 107 | rowGap: ALTERNATING_BOX_ROW_GAP_PX, |
| 108 | }} |
| 109 | > |
| 110 | {props.children} |
| 111 | </div> |
| 112 | </div> |
| 113 | </div> |
| 114 | </div> |
| 115 | </SlateElement> |
| 116 | ); |
| 117 | } |
| 118 | |
| 119 | return ( |
| 120 | <SlateElement {...props} className="mb-4"> |
| 121 | <div |
| 122 | className={cn( |
| 123 | "max-w-full items-stretch *:flex *:min-w-0", |
| 124 | columnSizeVariant({ columnSize }), |
| 125 | boxType === "joined" || boxType === "joined-icon" ? "" : "gap-4", |
| 126 | )} |
| 127 | > |
| 128 | {props.children} |
| 129 | </div> |
| 130 | </SlateElement> |
| 131 | ); |
| 132 | } |
| 133 |