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",