Add front component skeleton loader (#23261)

Front components (dashboard widget, side panel, settings preview) showed
blank space during their entire load. They now show a shimmering
full-area skeleton continuously, from the lazy chunk load through
metadata fetch, token/SDK wait, and worker boot, until the real UI
mounts.

The skeleton is threaded down as an optional `loadingFallback` prop so
the shared `twenty-front-component-renderer` package stays
dependency-free (react-loading-skeleton stays in twenty-front). The
command-menu headless component opts out by not passing a fallback, so
it stays blank as before.

<!-- This is an auto-generated description by cubic. -->
<a
href="https://cubic.dev/pr/twentyhq/twenty/pull/23261?utm_source=github"
target="_blank" rel="noopener noreferrer"
data-no-image-dialog="true"><picture><source
media="(prefers-color-scheme: dark)"
srcset="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"><source
media="(prefers-color-scheme: light)"
srcset="https://www.cubic.dev/buttons/review-in-cubic-light.svg"><img
alt="Review in cubic"
src="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"></picture></a>
<!-- End of auto-generated description by cubic. -->
This commit is contained in:
Raphaël Bosi
2026-07-24 14:09:16 +02:00
committed by GitHub
parent 3a8f086d15
commit 3ee8fc0973
6 changed files with 62 additions and 8 deletions
@@ -11,7 +11,7 @@ import {
type RemoteReceiver,
RemoteRootRenderer,
} from '@remote-dom/react/host';
import { useState } from 'react';
import { type ReactNode, useState } from 'react';
import { ErrorBoundary } from 'react-error-boundary';
import { isDefined } from 'twenty-shared/utils';
@@ -35,6 +35,7 @@ type FrontComponentRendererProps = {
frontComponentHostCommunicationApi: FrontComponentHostCommunicationApi;
onError: (error?: Error) => void;
colorScheme: 'light' | 'dark';
loadingFallback?: ReactNode;
};
export const FrontComponentRenderer = ({
@@ -48,6 +49,7 @@ export const FrontComponentRenderer = ({
frontComponentHostCommunicationApi,
onError,
colorScheme,
loadingFallback,
}: FrontComponentRendererProps) => {
const [receiver, setReceiver] = useState<RemoteReceiver | null>(null);
const [thread, setThread] = useState<FrontComponentThread | null>(null);
@@ -55,6 +57,8 @@ export const FrontComponentRenderer = ({
const [isExecutionContextInitialized, setIsExecutionContextInitialized] =
useState(false);
const isReady = isDefined(receiver) && isExecutionContextInitialized;
return (
<>
<FrontComponentWorkerEffect
@@ -100,7 +104,9 @@ export const FrontComponentRenderer = ({
</>
)}
{isDefined(receiver) && isExecutionContextInitialized && (
{!isDefined(error) && !isReady && loadingFallback}
{isReady && (
<ThemeProvider colorScheme={colorScheme}>
<ErrorBoundary
onError={setError}