Files
outline/shared/editor/components/Embed.tsx
T
Tom MoorandGitHub 08a3b64295 chore: Exhaustive deps warning -> error (#13124)
* chore: Exhaustive deps turned to error

* Refactor useDragResize

* Refactor Lightbox to explicit state machine

* feedback

* ci
2026-07-25 10:43:49 -04:00

141 lines
3.2 KiB
TypeScript

import * as React from "react";
import styled from "styled-components";
import type { EmbedDescriptor } from "../embeds";
import { getMatchingEmbed } from "../lib/embeds";
import type { ComponentProps } from "../types";
import DisabledEmbed from "./DisabledEmbed";
import Frame from "./Frame";
import { ResizeBottom, ResizeLeft, ResizeRight } from "./ResizeHandle";
import useDragResize from "./hooks/useDragResize";
type Props = ComponentProps & {
embeds: EmbedDescriptor[];
embedsDisabled?: boolean;
style?: React.CSSProperties;
onChangeSize?: (props: { width: number; height?: number }) => void;
};
const Embed = (props: Props) => {
const ref = React.useRef<HTMLDivElement>(null);
const { node, isEditable, embedsDisabled, onChangeSize } = props;
const naturalWidth = 0;
const naturalHeight = 400;
const isResizable = !!onChangeSize && !embedsDisabled;
const { width, height, handlePointerDown, dragging } = useDragResize({
width: node.attrs.width ?? naturalWidth,
height: node.attrs.height ?? naturalHeight,
naturalWidth,
naturalHeight,
onChangeSize,
ref,
});
const style: React.CSSProperties = {
width: width || "100%",
height: height || 400,
maxWidth: "100%",
pointerEvents: dragging ? "none" : "all",
};
return (
<FrameWrapper ref={ref} $dragging={!!dragging}>
<InnerEmbed style={style} {...props} />
{isEditable && isResizable && (
<>
<ResizeBottom
onPointerDown={handlePointerDown("bottom")}
$dragging={!!dragging}
/>
</>
)}
</FrameWrapper>
);
};
function InnerEmbed({
isEditable,
isSelected,
node,
embeds,
embedsDisabled,
style,
}: Props) {
const cache = React.useMemo(
() => getMatchingEmbed(embeds, node.attrs.href),
[embeds, node.attrs.href]
);
if (!cache) {
return null;
}
const { embed, matches } = cache;
if (embedsDisabled) {
return (
<DisabledEmbed
href={node.attrs.href}
embed={embed}
isEditable={isEditable}
isSelected={isSelected}
/>
);
}
if (embed.transformMatch) {
const src = embed.transformMatch(matches);
return (
<Frame
src={src}
style={style}
isSelected={isSelected}
canonicalUrl={embed.hideToolbar ? undefined : node.attrs.href}
title={embed.title}
referrerPolicy="strict-origin-when-cross-origin"
border
/>
);
}
if ("component" in embed) {
return (
// @ts-expect-error Component type
<embed.component
attrs={node.attrs}
style={style}
matches={matches}
isEditable={isEditable}
isSelected={isSelected}
embed={embed}
/>
);
}
return null;
}
const FrameWrapper = styled.div<{ $dragging: boolean }>`
line-height: 0;
position: relative;
margin-left: auto;
margin-right: auto;
white-space: nowrap;
cursor: default;
border-radius: 8px;
user-select: none;
max-width: 100%;
transition-property: width, max-height;
transition-duration: ${(props) => (props.$dragging ? "0ms" : "150ms")};
transition-timing-function: ease-in-out;
&:hover {
${ResizeLeft}, ${ResizeRight} {
opacity: 1;
}
}
`;
export default Embed;