| 1 | import { type Path } from "platejs"; |
| 2 | import { |
| 3 | ElementStatic, |
| 4 | SlateElement, |
| 5 | type SlateElementProps, |
| 6 | } from "platejs/static"; |
| 7 | import { type ReactNode } from "react"; |
| 8 | |
| 9 | import { cn } from "@/lib/utils"; |
| 10 | import { type TCycleItemElement } from "../../plugins/cycle-plugin"; |
| 11 | import { CycleWheel } from "../cycle-element"; |
| 12 | import { |
| 13 | CYCLE_WHEEL_SIZE_PX, |
| 14 | CycleContext, |
| 15 | useCycleFitScale, |
| 16 | type CycleColumnSide, |
| 17 | } from "../cycle-element"; |
| 18 | |
| 19 | function StaticCycleColumn({ |
| 20 | children, |
| 21 | isMultiColumn, |
| 22 | side, |
| 23 | }: { |
| 24 | children: ReactNode[]; |
| 25 | isMultiColumn: boolean; |
| 26 | side: CycleColumnSide; |
| 27 | }) { |
| 28 | const contextValue = { isMultiColumn, side }; |
| 29 | |
| 30 | if (children.length === 0) { |
| 31 | return <div className="min-w-0" aria-hidden="true" />; |
| 32 | } |
| 33 | |
| 34 | return ( |
| 35 | <CycleContext.Provider value={contextValue}> |
| 36 | <div |
| 37 | className={cn( |
| 38 | "flex min-w-0 flex-col justify-center", |
| 39 | isMultiColumn ? "h-full gap-4" : "gap-1", |
| 40 | )} |
| 41 | > |
| 42 | {children} |
| 43 | </div> |
| 44 | </CycleContext.Provider> |
| 45 | ); |
| 46 | } |
| 47 | |
| 48 | const EMPTY_DECORATE = () => []; |
| 49 | |
| 50 | type StaticCycleItemEntry = { |
| 51 | item: TCycleItemElement; |
| 52 | path: Path; |
| 53 | }; |
| 54 | |
| 55 | function renderStaticCycleItems( |
| 56 | editor: SlateElementProps["editor"], |
| 57 | entries: StaticCycleItemEntry[], |
| 58 | ) { |
| 59 | return entries.map(({ item, path }, index) => { |
| 60 | const key = |
| 61 | typeof item.id === "string" || typeof item.id === "number" |
| 62 | ? item.id |
| 63 | : `cycle-item-${path.at(-1) ?? index}`; |
| 64 | |
| 65 | return ( |
| 66 | <div key={key} className="min-w-0"> |
| 67 | <ElementStatic |
| 68 | decorate={EMPTY_DECORATE} |
| 69 | decorations={[]} |
| 70 | editor={editor} |
| 71 | element={item} |
| 72 | path={path} |
| 73 | /> |
| 74 | </div> |
| 75 | ); |
| 76 | }); |
| 77 | } |
| 78 | |
| 79 | export function CycleElementStatic(props: SlateElementProps) { |
| 80 | const element = props.element as { |
| 81 | alignment?: "left" | "center" | "right"; |
| 82 | variant?: "cycle" | "flower" | "ring" | "circle"; |
| 83 | children?: unknown[]; |
| 84 | }; |
| 85 | const { alignment = "center", variant = "cycle" } = element; |
| 86 | const cycleItems = (element.children ?? []) as TCycleItemElement[]; |
| 87 | const cycleItemEntries = cycleItems.map((item, index) => ({ |
| 88 | item, |
| 89 | path: [...props.path, index], |
| 90 | })); |
| 91 | const totalChildren = cycleItems.length; |
| 92 | const totalSegments = totalChildren > 0 ? totalChildren : 6; |
| 93 | const { containerRef, layoutRef, frameStyle, fitStyle } = |
| 94 | useCycleFitScale<HTMLDivElement>(); |
| 95 | const leftChildren = renderStaticCycleItems( |
| 96 | props.editor, |
| 97 | cycleItemEntries.filter((_, index) => index % 2 === 0), |
| 98 | ); |
| 99 | const rightChildren = renderStaticCycleItems( |
| 100 | props.editor, |
| 101 | cycleItemEntries.filter((_, index) => index % 2 === 1), |
| 102 | ); |
| 103 | |
| 104 | return ( |
| 105 | <SlateElement {...props} className={cn("relative my-0", props.className)}> |
| 106 | <div |
| 107 | className={cn( |
| 108 | "flex w-full", |
| 109 | alignment === "left" && "justify-start", |
| 110 | alignment === "right" && "justify-end", |
| 111 | alignment === "center" && "justify-center", |
| 112 | )} |
| 113 | > |
| 114 | <div |
| 115 | ref={containerRef} |
| 116 | className="@container/cycle-layout mx-auto w-full" |
| 117 | > |
| 118 | <div className="mx-auto" style={frameStyle}> |
| 119 | <div |
| 120 | ref={layoutRef} |
| 121 | className="grid grid-cols-[minmax(0,1fr)_auto_minmax(0,1fr)] items-stretch gap-4" |
| 122 | style={fitStyle} |
| 123 | > |
| 124 | <StaticCycleColumn isMultiColumn side="left"> |
| 125 | {leftChildren} |
| 126 | </StaticCycleColumn> |
| 127 | |
| 128 | <CycleWheel |
| 129 | items={cycleItems} |
| 130 | sizePx={CYCLE_WHEEL_SIZE_PX} |
| 131 | totalSegments={totalSegments} |
| 132 | variant={variant} |
| 133 | /> |
| 134 | |
| 135 | <StaticCycleColumn isMultiColumn side="right"> |
| 136 | {rightChildren} |
| 137 | </StaticCycleColumn> |
| 138 | </div> |
| 139 | </div> |
| 140 | </div> |
| 141 | </div> |
| 142 | </SlateElement> |
| 143 | ); |
| 144 | } |
| 145 |