diff --git a/frontend/lib/src/components/widgets/CustomComponent/ComponentInstance.test.tsx b/frontend/lib/src/components/widgets/CustomComponent/ComponentInstance.test.tsx index a69a6380e579..572c2179e45e 100644 --- a/frontend/lib/src/components/widgets/CustomComponent/ComponentInstance.test.tsx +++ b/frontend/lib/src/components/widgets/CustomComponent/ComponentInstance.test.tsx @@ -146,6 +146,54 @@ describe("ComponentInstance", () => { expect(iframe).toHaveAttribute("sandbox", DEFAULT_IFRAME_SANDBOX_POLICY) }) + it("displays a skeleton initially with a certain height", () => { + const componentRegistry = getComponentRegistry() + render( + + ) + const skeleton = screen.getByTestId("stSkeleton") + expect(skeleton).toBeInTheDocument() + expect(skeleton).toHaveStyle("height: 2.75rem") + + const iframe = screen.getByTitle(MOCK_COMPONENT_NAME) + expect(iframe).toHaveAttribute("height", "0") + }) + + it("will not displays a skeleton when height is explicitly set to 0", () => { + const componentRegistry = getComponentRegistry() + render( + + ) + expect(screen.queryByTestId("stSkeleton")).not.toBeInTheDocument() + + const iframe = screen.getByTitle(MOCK_COMPONENT_NAME) + expect(iframe).toHaveAttribute("height", "0") + }) + describe("COMPONENT_READY handler", () => { it("posts a RENDER message to the iframe", () => { const jsonArgs = { foo: "string", bar: 5 } @@ -184,6 +232,43 @@ describe("ComponentInstance", () => { expect(postMessage).toHaveBeenCalledWith(renderMsg(jsonArgs, []), "*") }) + it("hides the skeleton and maintains iframe height of 0", () => { + const componentRegistry = getComponentRegistry() + render( + + ) + + const iframe = screen.getByTitle(MOCK_COMPONENT_NAME) + + // SET COMPONENT_READY + fireEvent( + window, + new MessageEvent("message", { + data: { + isStreamlitMessage: true, + apiVersion: 1, + type: ComponentMessageType.COMPONENT_READY, + }, + // @ts-expect-error + source: iframe.contentWindow, + }) + ) + expect(screen.queryByTestId("stSkeleton")).not.toBeInTheDocument() + expect(iframe).toHaveAttribute("height", "0") + }) + it("prevents RENDER message until component is ready", () => { const jsonArgs = { foo: "string", bar: 5 } const componentRegistry = getComponentRegistry() diff --git a/frontend/lib/src/components/widgets/CustomComponent/ComponentInstance.tsx b/frontend/lib/src/components/widgets/CustomComponent/ComponentInstance.tsx index 4db1fb805138..36d6e82a35da 100644 --- a/frontend/lib/src/components/widgets/CustomComponent/ComponentInstance.tsx +++ b/frontend/lib/src/components/widgets/CustomComponent/ComponentInstance.tsx @@ -119,24 +119,6 @@ function getWarnMessage(componentName: string, url?: string): string { return message } -type HeightUnit = "px" -/** - * Parse the height as a string and add the unit as a suffix, e.g. `(42) => '42px'` - * - * @param height height number - * @returns the height number as a string with suffix or `undefined` if `height` is `undefined` - */ -function parseToStringWithUnitSuffix( - height?: number, - unit: HeightUnit = "px" -): string | undefined { - if (!height) { - return undefined - } - - return `${height}${unit}` -} - function tryParseArgs( jsonArgs: string, specialArgs: ISpecialArg[], @@ -215,7 +197,8 @@ function ComponentInstance(props: Props): ReactElement { const [isReadyTimeout, setIsReadyTimeout] = useState() // By passing the args.height here, we can derive the initial height for // custom components that define a height property, e.g. in Python - // my_custom_component(height=100) + // my_custom_component(height=100). undefined means no explicit height + // was specified, but will be set to the default height of 0. const [frameHeight, setFrameHeight] = useState( isNaN(parsedNewArgs.height) ? undefined : parsedNewArgs.height ) @@ -351,9 +334,15 @@ function ComponentInstance(props: Props): ReactElement { // Show the loading Skeleton while we have not received the ready message from the custom component // but while we also have not waited until the ready timeout - const loadingSkeleton = !isReadyRef.current && !isReadyTimeout && ( - - ) + const loadingSkeleton = !isReadyRef.current && + !isReadyTimeout && + // if height is explicitly set to 0, we don’t want to show the skeleton at all + frameHeight !== 0 && ( + // Skeletons will have a default height if no frameHeight was specified + + ) // If we've timed out waiting for the READY message from the component, // display a warning. @@ -389,7 +378,8 @@ function ComponentInstance(props: Props): ReactElement { ref={iframeRef} src={getSrc(componentName, registry, url)} width={width} - height={frameHeight} + // for undefined height we set the height to 0 to avoid inconsistent behavior + height={frameHeight ?? 0} style={{ colorScheme: "light dark", display: isReadyRef.current ? "initial" : "none",