chore: add measured container

This commit is contained in:
Aslam H
2024-10-20 09:05:02 +07:00
parent 8c2669791c
commit 200245b39f
4 changed files with 111 additions and 14 deletions

View File

@@ -0,0 +1,39 @@
import * as React from "react"
import { useContainerSize } from "../hooks/use-container-size"
interface MeasuredContainerProps<T extends React.ElementType> {
as: T
name: string
children?: React.ReactNode
}
export const MeasuredContainer = React.forwardRef(
<T extends React.ElementType>(
{
as: Component,
name,
children,
style = {},
...props
}: MeasuredContainerProps<T> & React.ComponentProps<T>,
ref: React.Ref<HTMLElement>,
) => {
const innerRef = React.useRef<HTMLElement>(null)
const rect = useContainerSize(innerRef.current)
React.useImperativeHandle(ref, () => innerRef.current as HTMLElement)
const customStyle = {
[`--${name}-width`]: `${rect.width}px`,
[`--${name}-height`]: `${rect.height}px`,
}
return (
<Component {...props} ref={innerRef} style={{ ...customStyle, ...style }}>
{children}
</Component>
)
},
)
MeasuredContainer.displayName = "MeasuredContainer"

View File

@@ -4,6 +4,7 @@ import { Content } from "@tiptap/core"
import { BubbleMenu } from "./components/bubble-menu"
import { cn } from "@/lib/utils"
import { useLaEditor, UseLaEditorProps } from "./hooks/use-la-editor"
import { MeasuredContainer } from "./components/measured-container"
export interface LaEditorProps extends UseLaEditorProps {
value?: Content
@@ -21,7 +22,9 @@ export const LaEditor = React.memo(
}
return (
<div
<MeasuredContainer
as="div"
name="editor"
className={cn("relative flex h-full w-full grow flex-col", className)}
ref={ref}
>
@@ -30,7 +33,7 @@ export const LaEditor = React.memo(
className={cn("la-editor", editorContentClassName)}
/>
<BubbleMenu editor={editor} />
</div>
</MeasuredContainer>
)
},
),

View File

@@ -4,13 +4,12 @@ import type { ElementDimensions } from "../hooks/use-drag-resize"
import { useDragResize } from "../hooks/use-drag-resize"
import { ResizeHandle } from "./resize-handle"
import { cn } from "@/lib/utils"
import { NodeSelection } from "@tiptap/pm/state"
import { Controlled as ControlledZoom } from "react-medium-image-zoom"
import { ActionButton, ActionWrapper, ImageActions } from "./image-actions"
import { useImageActions } from "../hooks/use-image-actions"
import { blobUrlToBase64 } from "@shared/editor/lib/utils"
import { InfoCircledIcon, TrashIcon } from "@radix-ui/react-icons"
import { ImageOverlay } from "./image-overlay"
import { blobUrlToBase64 } from "@shared/editor/lib/utils"
import { Spinner } from "@shared/components/spinner"
const MAX_HEIGHT = 600
@@ -29,7 +28,6 @@ interface ImageState {
export const ImageViewBlock: React.FC<NodeViewProps> = ({
editor,
node,
getPos,
selected,
updateAttributes,
}) => {
@@ -52,23 +50,23 @@ export const ImageViewBlock: React.FC<NodeViewProps> = ({
"left" | "right" | null
>(null)
const focus = React.useCallback(() => {
const { view } = editor
const $pos = view.state.doc.resolve(getPos())
view.dispatch(view.state.tr.setSelection(new NodeSelection($pos)))
}, [editor, getPos])
const onDimensionsChange = React.useCallback(
({ width, height }: ElementDimensions) => {
focus()
updateAttributes({ width, height })
},
[focus, updateAttributes],
[updateAttributes],
)
const aspectRatio =
imageState.naturalSize.width / imageState.naturalSize.height
const maxWidth = MAX_HEIGHT * aspectRatio
const containerMaxWidth = containerRef.current
? parseFloat(
getComputedStyle(containerRef.current).getPropertyValue(
"--editor-width",
),
)
: Infinity
const { isLink, onView, onDownload, onCopy, onCopyLink, onRemoveImg } =
useImageActions({
@@ -94,7 +92,7 @@ export const ImageViewBlock: React.FC<NodeViewProps> = ({
onDimensionsChange,
minWidth: MIN_WIDTH,
minHeight: MIN_HEIGHT,
maxWidth,
maxWidth: containerMaxWidth > 0 ? containerMaxWidth : maxWidth,
})
const shouldMerge = React.useMemo(() => currentWidth <= 180, [currentWidth])
@@ -182,6 +180,8 @@ export const ImageViewBlock: React.FC<NodeViewProps> = ({
}))
}
}
URL.revokeObjectURL(initialSrc)
}
}

View File

@@ -0,0 +1,55 @@
import { useState, useEffect, useCallback } from "react"
const DEFAULT_RECT: DOMRect = {
top: 0,
left: 0,
bottom: 0,
right: 0,
x: 0,
y: 0,
width: 0,
height: 0,
toJSON: () => "{}",
}
export function useContainerSize(element: HTMLElement | null): DOMRect {
const [size, setSize] = useState<DOMRect>(
() => element?.getBoundingClientRect() ?? DEFAULT_RECT,
)
const handleResize = useCallback(() => {
if (!element) return
const newRect = element.getBoundingClientRect()
setSize((prevRect) => {
if (
Math.round(prevRect.width) === Math.round(newRect.width) &&
Math.round(prevRect.height) === Math.round(newRect.height) &&
Math.round(prevRect.x) === Math.round(newRect.x) &&
Math.round(prevRect.y) === Math.round(newRect.y)
) {
return prevRect
}
return newRect
})
}, [element])
useEffect(() => {
if (!element) return
const resizeObserver = new ResizeObserver(handleResize)
resizeObserver.observe(element)
window.addEventListener("click", handleResize)
window.addEventListener("resize", handleResize)
return () => {
resizeObserver.disconnect()
window.removeEventListener("click", handleResize)
window.removeEventListener("resize", handleResize)
}
}, [element, handleResize])
return size
}