Repository: 4ian/GDevelop
Pull request #7482: Add tooltip for tabs
Author: login: AlexandreSi
Created: 2025-03-18T23:50:34Z | Merged: 2025-03-20T17:27:38Z



https://github.com/user-attachments/assets/6dec0bdc-3497-46d0-b2d7-0f07fe5e18b0




Diff:
diff --git a/newIDE/app/src/MainFrame/EditorContainers/HomePage/LearnSection/CourseSection.js b/newIDE/app/src/MainFrame/EditorContainers/HomePage/LearnSection/CourseSection.js
index feba14b6f448..50e288ea45ac 100644
--- a/newIDE/app/src/MainFrame/EditorContainers/HomePage/LearnSection/CourseSection.js
+++ b/newIDE/app/src/MainFrame/EditorContainers/HomePage/LearnSection/CourseSection.js
@@ -330,7 +330,7 @@ const CourseSection = ({
                       style={styles.desktopTableOfContent}
                     >
                       <Text noMargin size="sub-title">
-                        Chapters
+                        <Trans>Chapters</Trans>
                       </Text>
                       {courseCompletion !== null && (
                         <Line noMargin>
diff --git a/newIDE/app/src/MainFrame/EditorTabs/DraggableEditorTabs.js b/newIDE/app/src/MainFrame/EditorTabs/DraggableEditorTabs.js
index 71ec02ab2c45..fd7cb48c1e34 100644
--- a/newIDE/app/src/MainFrame/EditorTabs/DraggableEditorTabs.js
+++ b/newIDE/app/src/MainFrame/EditorTabs/DraggableEditorTabs.js
@@ -33,9 +33,10 @@ type DraggableEditorTabsProps = {|
   onCloseAll: () => void,
   onTabActivated: (editor: EditorTab) => void,
   onDropTab: (fromIndex: number, toHoveredIndex: number) => void,
+  onHoverTab: (editor: ?EditorTab) => void,
 |};
 
-const getTabId = (editorTab: EditorTab) =>
+export const getTabId = (editorTab: EditorTab) =>
   `tab-${editorTab.key.replace(/\s/g, '-')}`;
 
 const homeTabApproximateWidth = 35;
@@ -49,6 +50,7 @@ export function DraggableEditorTabs({
   onCloseAll,
   onTabActivated,
   onDropTab,
+  onHoverTab,
 }: DraggableEditorTabsProps) {
   let draggedTabIndex: ?number = null;
 
@@ -108,6 +110,7 @@ export function DraggableEditorTabs({
               onClose={() => onCloseTab(editorTab)}
               onCloseOthers={() => onCloseOtherTabs(editorTab)}
               onCloseAll={onCloseAll}
+              onHover={(enter: boolean) => onHoverTab(enter ? editorTab : null)}
               onActivated={() => onTabActivated(editorTab)}
               closable={editorTab.closable}
               onBeginDrag={() => {
@@ -152,6 +155,7 @@ export function DraggableClosableTab({
   onActivated,
   onBeginDrag,
   onDrop,
+  onHover,
   maxWidth,
 }: DraggableClosableTabProps) {
   return (
@@ -190,6 +194,7 @@ export function DraggableClosableTab({
                   renderCustomIcon={renderCustomIcon}
                   closable={closable}
                   onClick={onClick}
+                  onHover={onHover}
                   onActivated={onActivated}
                   maxWidth={maxWidth}
                   key={id}
diff --git a/newIDE/app/src/MainFrame/TabsTitlebar.js b/newIDE/app/src/MainFrame/TabsTitlebar.js
index 574c947bc23e..039e2c4d3896 100644
--- a/newIDE/app/src/MainFrame/TabsTitlebar.js
+++ b/newIDE/app/src/MainFrame/TabsTitlebar.js
@@ -1,5 +1,6 @@
 // @flow
 import * as React from 'react';
+
 import MenuIcon from '../UI/CustomSvgIcons/Menu';
 import IconButton from '../UI/IconButton';
 import GDevelopThemeContext from '../UI/Theme/GDevelopThemeContext';
@@ -8,6 +9,10 @@ import {
   TitleBarLeftSafeMargins,
   TitleBarRightSafeMargins,
 } from '../UI/TitleBarSafeMargins';
+import { type EditorTab } from './EditorTabs/EditorTabsHandler';
+import { getTabId } from './EditorTabs/DraggableEditorTabs';
+import { useScreenType } from '../UI/Responsive/ScreenTypeMeasurer';
+import TabsTitlebarTooltip from './TabsTitlebarTooltip';
 
 const WINDOW_DRAGGABLE_PART_CLASS_NAME = 'title-bar-draggable-part';
 const WINDOW_NON_DRAGGABLE_PART_CLASS_NAME = 'title-bar-non-draggable-part';
@@ -30,21 +35,27 @@ const styles = {
 };
 
 type TabsTitlebarProps = {|
-  children: React.Node,
   hidden: boolean,
   toggleProjectManager: () => void,
+  renderTabs: (onHoverEditorTab: (?EditorTab) => void) => React.Node,
 |};
 
 /**
  * The titlebar containing a menu, the tabs and giving space for window controls.
  */
 export default function TabsTitlebar({
-  children,
   toggleProjectManager,
   hidden,
+  renderTabs,
 }: TabsTitlebarProps) {
+  const isTouchscreen = useScreenType() === 'touch';
   const gdevelopTheme = React.useContext(GDevelopThemeContext);
   const backgroundColor = gdevelopTheme.titlebar.backgroundColor;
+  const [tooltipData, setTooltipData] = React.useState<?{|
+    element: HTMLElement,
+    editorTab: EditorTab,
+  |}>(null);
+  const tooltipTimeoutId = React.useRef<?TimeoutID>(null);
 
   React.useEffect(
     () => {
@@ -53,6 +64,51 @@ export default function TabsTitlebar({
     [backgroundColor]
   );
 
+  const onHoverEditorTab = React.useCallback(
+    (editorTab: ?EditorTab) => {
+      if (isTouchscreen) {
+        setTooltipData(null);
+        return;
+      }
+
+      if (tooltipTimeoutId.current) {
+        clearTimeout(tooltipTimeoutId.current);
+        tooltipTimeoutId.current = null;
+      }
+
+      if (editorTab) {
+        const element = document.getElementById(getTabId(editorTab));
+        if (element) {
+          tooltipTimeoutId.current = setTimeout(
+            () => {
+              setTooltipData({ editorTab, element });
+            },
+            // If the tooltip is already displayed, quickly change to the new tab
+            // but not too quick because the display might look flickering.
+            tooltipData ? 100 : 500
+          );
+        }
+      } else {
+        tooltipTimeoutId.current = setTimeout(() => {
+          setTooltipData(null);
+        }, 50);
+      }
+    },
+    [isTouchscreen, tooltipData]
+  );
+
+  React.useEffect(
+    () => {
+      return () => {
+        if (tooltipTimeoutId.current) {
+          clearTimeout(tooltipTimeoutId.current);
+        }
+      };
+    },
+    // Clear timeout if necessary when unmounting.
+    []
+  );
+
   return (
     <div
       style={{
@@ -77,8 +133,14 @@ export default function TabsTitlebar({
       >
         <MenuIcon />
       </IconButton>
-      {children}
+      {renderTabs(onHoverEditorTab)}
       <TitleBarRightSafeMargins />
+      {tooltipData && (
+        <TabsTitlebarTooltip
+          anchorElement={tooltipData.element}
+          editorTab={tooltipData.editorTab}
+        />
+      )}
     </div>
   );
 }
diff --git a/newIDE/app/src/MainFrame/TabsTitlebarTooltip.js b/newIDE/app/src/MainFrame/TabsTitlebarTooltip.js
new file mode 100644
index 000000000000..ed2043947b20
--- /dev/null
+++ b/newIDE/app/src/MainFrame/TabsTitlebarTooltip.js
@@ -0,0 +1,162 @@
+// @flow
+import * as React from 'react';
+import { Trans } from '@lingui/macro';
+import Fade from '@material-ui/core/Fade';
+import Paper from '@material-ui/core/Paper';
+import Popper from '@material-ui/core/Popper';
+
+import GDevelopThemeContext from '../UI/Theme/GDevelopThemeContext';
+import {
+  getEditorTabMetadata,
+  type EditorTab,
+  type EditorKind,
+} from './EditorTabs/EditorTabsHandler';
+import { ColumnStackLayout } from '../UI/Layout';
+import { Line } from '../UI/Grid';
+import Text from '../UI/Text';
+
+const editorKindToLabel: { [kind: EditorKind]: React.Node } = {
+  layout: <Trans>Scene</Trans>,
+  'layout events': <Trans>Scene events</Trans>,
+  'external layout': <Trans>External layout</Trans>,
+  'external events': <Trans>External events</Trans>,
+  'events functions extension': <Trans>Extension</Trans>,
+  'custom object': <Trans>Object</Trans>,
+  debugger: <Trans>Debugger</Trans>,
+  resources: <Trans>Resources</Trans>,
+  'start page': <Trans>Homepage</Trans>,
+};
+
+const styles = {
+  paper: {
+    padding: '8px 10px',
+    minWidth: 180,
+  },
+  tabIcon: {
+    marginLeft: 4,
+    marginRight: 4,
+    display: 'flex',
+  },
+  emptyTabIcon: {
+    marginLeft: 4,
+    marginRight: 4,
+    height: 20,
+    width: 24,
+    display: 'flex',
+  },
+  tooltip: {
+    zIndex: 3,
+    maxWidth: 'min(90%, 300px)',
+  },
+};
+
+type Props = {|
+  anchorElement: HTMLElement,
+  editorTab: EditorTab,
+|};
+
+const TabsTitlebarTooltip = ({ anchorElement, editorTab }: Props) => {
+  const gdevelopTheme = React.useContext(GDevelopThemeContext);
+  const [tooltipStyle, setTooltipStyle] = React.useState<Object>(
+    styles.tooltip
+  );
+
+  const brightness = gdevelopTheme.palette.type === 'dark' ? 0.978 : 0.224;
+
+  const editorTabMetadata = getEditorTabMetadata(editorTab);
+
+  React.useEffect(
+    () => {
+      const timeoutId = setTimeout(() => {
+        setTooltipStyle(currentStyle => ({
+          ...currentStyle,
+          transition: 'transform 150ms ease-in-out',
+        }));
+      }, 100);
+      return () => clearTimeout(timeoutId);
+    },
+    // Apply transition after first display of tooltip to avoid having the
+    // transition weirdly applied from the 0;0 coordinates.
+    []
+  );
+
+  const title = editorKindToLabel[editorTabMetadata.editorKind];
+  let subtitle = null;
+  if (
+    [
+      'layout',
+      'layout events',
+      'external layout',
+      'external events',
+      'events functions extension',
+    ].includes(editorTabMetadata.editorKind)
+  ) {
+    subtitle = editorTabMetadata.projectItemName;
+  } else if (
+    editorTabMetadata.editorKind === 'custom object' &&
+    editorTabMetadata.projectItemName
+  ) {
+    const nameParts = editorTabMetadata.projectItemName.split('::');
+    const customObjectName = nameParts[1];
+    if (customObjectName) {
+      subtitle = customObjectName;
+    }
+  }
+
+  return (
+    <Popper
+      id="tabs-titlebar-tooltip"
+      open={true}
+      anchorEl={anchorElement}
+      transition
+      placement={'bottom-start'}
+      popperOptions={{
+        modifiers: {
+          arrow: { enabled: false },
+          offset: {
+            enabled: true,
+            offset: '0,5',
+          },
+          preventOverflow: {
+            enabled: true,
+            boundariesElement: document.querySelector('.main-frame'),
+          },
+        },
+      }}
+      style={tooltipStyle}
+    >
+      {({ TransitionProps }) => (
+        <Fade {...TransitionProps} timeout={{ enter: 350, exit: 0 }}>
+          <Paper
+            style={{
+              ...styles.paper,
+              backgroundColor: gdevelopTheme.paper.backgroundColor.light,
+            }}
+            elevation={4}
+          >
+            <ColumnStackLayout noMargin>
+              <Line alignItems="center" noMargin>
+                {editorTab.icon || editorTab.renderCustomIcon ? (
+                  <span style={styles.tabIcon}>
+                    {editorTab.renderCustomIcon
+                      ? editorTab.renderCustomIcon(brightness)
+                      : editorTab.icon}
+                  </span>
+                ) : null}
+                <Text noMargin>{title}</Text>
+              </Line>
+              {subtitle && (
+                <Line alignItems="center" noMargin>
+                  <span style={styles.emptyTabIcon} />
+                  <Text noMargin>{subtitle}</Text>
+                </Line>
+              )}
+            </ColumnStackLayout>
+          </Paper>
+        </Fade>
+      )}
+    </Popper>
+  );
+};
+
+export default TabsTitlebarTooltip;
diff --git a/newIDE/app/src/MainFrame/index.js b/newIDE/app/src/MainFrame/index.js
index 568df82f1e51..b7067534d860 100644
--- a/newIDE/app/src/MainFrame/index.js
+++ b/newIDE/app/src/MainFrame/index.js
@@ -3697,22 +3697,24 @@ const MainFrame = (props: Props) => {
       <TabsTitlebar
         hidden={tabsTitleBarAndEditorToolbarHidden}
         toggleProjectManager={toggleProjectManager}
-      >
-        <DraggableEditorTabs
-          hideLabels={false}
-          editorTabs={state.editorTabs}
-          onClickTab={(id: number) => _onChangeEditorTab(id)}
-          onCloseTab={(editorTab: EditorTab) => _onCloseEditorTab(editorTab)}
-          onCloseOtherTabs={(editorTab: EditorTab) =>
-            _onCloseOtherEditorTabs(editorTab)
-          }
-          onCloseAll={_onCloseAllEditorTabs}
-          onTabActivated={(editorTab: EditorTab) =>
-            _onEditorTabActivated(editorTab)
-          }
-          onDropTab={onDropEditorTab}
-        />
-      </TabsTitlebar>
+        renderTabs={onHoverEditorTab => (
+          <DraggableEditorTabs
+            hideLabels={false}
+            editorTabs={state.editorTabs}
+            onClickTab={(id: number) => _onChangeEditorTab(id)}
+            onCloseTab={(editorTab: EditorTab) => _onCloseEditorTab(editorTab)}
+            onCloseOtherTabs={(editorTab: EditorTab) =>
+              _onCloseOtherEditorTabs(editorTab)
+            }
+            onCloseAll={_onCloseAllEditorTabs}
+            onTabActivated={(editorTab: EditorTab) =>
+              _onEditorTabActivated(editorTab)
+            }
+            onDropTab={onDropEditorTab}
+            onHoverTab={onHoverEditorTab}
+          />
+        )}
+      />
       <Toolbar
         ref={toolbar}
         hidden={tabsTitleBarAndEditorToolbarHidden}
diff --git a/newIDE/app/src/UI/ClosableTabs.js b/newIDE/app/src/UI/ClosableTabs.js
index f5d8b865457e..cb9ce3fd3b16 100644
--- a/newIDE/app/src/UI/ClosableTabs.js
+++ b/newIDE/app/src/UI/ClosableTabs.js
@@ -140,6 +140,7 @@ export type ClosableTabProps = {|
   onCloseAll: () => void,
   onClick: () => void,
   onActivated: () => void,
+  onHover: boolean => void,
   maxWidth: number,
 |};
 
@@ -156,6 +157,7 @@ export function ClosableTab({
   closable,
   onClick,
   onActivated,
+  onHover,
   maxWidth,
 }: ClosableTabProps) {
   React.useEffect(
@@ -248,6 +250,8 @@ export function ClosableTab({
         // A tab lives in the top bar, which has the ability to drag the app window.
         // Ensure the tab does not have this ability, as it can be dragged itself.
         className={WINDOW_NON_DRAGGABLE_PART_CLASS_NAME}
+        onMouseEnter={() => onHover(true)}
+        onMouseLeave={() => onHover(false)}
       >
         <ButtonBase
           onClick={onClick}
@@ -281,7 +285,6 @@ export function ClosableTab({
                   ...styles.tabLabel,
                   maxWidth: labelMaxWidth,
                 }}
-                title={label}
               >
                 {label}
               </span>
diff --git a/newIDE/app/src/stories/componentStories/ClosableTabs.stories.js b/newIDE/app/src/stories/componentStories/ClosableTabs.stories.js
index 85df29977d86..2bb8f7458eaa 100644
--- a/newIDE/app/src/stories/componentStories/ClosableTabs.stories.js
+++ b/newIDE/app/src/stories/componentStories/ClosableTabs.stories.js
@@ -36,6 +36,7 @@ export const ThreeTabs = () => (
                 <>
                   <ClosableTab
                     onActivated={action('Tab 1 activated')}
+                    onHover={action('onHover')}
                     closable={false}
                     active={value === 0}
                     onClick={() => onChange(0)}
@@ -48,6 +49,7 @@ export const ThreeTabs = () => (
                   />
                   <ClosableTab
                     onActivated={action('Tab 2 activated')}
+                    onHover={action('onHover')}
                     closable
                     active={value === 1}
                     onClick={() => onChange(1)}
@@ -60,6 +62,7 @@ export const ThreeTabs = () => (
                   />
                   <ClosableTab
                     onActivated={action('Tab 3 activated')}
+                    onHover={action('onHover')}
                     closable
                     active={value === 2}
                     onClick={() => onChange(2)}
@@ -119,6 +122,7 @@ export const LongLabels = () => (
                 <>
                   <ClosableTab
                     onActivated={action('Tab 1 activated')}
+                    onHover={action('onHover')}
                     closable
                     active={value === 0}
                     label="Tab 1 with a very very long label"
@@ -131,6 +135,7 @@ export const LongLabels = () => (
                   />
                   <ClosableTab
                     onActivated={action('Tab 2 activated')}
+                    onHover={action('onHover')}
                     closable
                     active={value === 1}
                     onClick={() => onChange(1)}
@@ -143,6 +148,7 @@ export const LongLabels = () => (
                   />
                   <ClosableTab
                     onActivated={action('Tab 3 activated')}
+                    onHover={action('onHover')}
                     closable
                     active={value === 2}
                     onClick={() => onChange(2)}
@@ -155,6 +161,7 @@ export const LongLabels = () => (
                   />
                   <ClosableTab
                     onActivated={action('Tab 4 activated')}
+                    onHover={action('onHover')}
                     closable
                     active={value === 3}
                     onClick={() => onChange(3)}
@@ -232,6 +239,7 @@ export const WithObjectsList = () => (
                 renderTabs={({ containerWidth }) => [
                   <ClosableTab
                     onActivated={action('Tab 1 activated')}
+                    onHover={action('onHover')}
                     closable
                     active={value === 0}
                     label="Tab 1"
@@ -244,6 +252,7 @@ export const WithObjectsList = () => (
                   />,
                   <ClosableTab
                     onActivated={action('Tab 2 activated')}
+                    onHover={action('onHover')}
                     closable
                     active={value === 1}
                     label="Tab 2"
@@ -256,6 +265,7 @@ export const WithObjectsList = () => (
                   />,
                   <ClosableTab
                     onActivated={action('Tab 3 activated')}
+                    onHover={action('onHover')}
                     closable
                     active={value === 2}
                     label="Tab 3"


Reviews:
user: login: ClementPasteau
state: APPROVED
author_association: COLLABORATOR
submitted_at: 2025-03-20T16:24:10Z
user: login: AlexandreSi
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-20T16:40:38Z
user: login: AlexandreSi
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-20T16:41:10Z

Review comments:
diff_hunk: @@ -27,24 +55,148 @@ const styles = {
     width: 34,
     height: 34,
   },
+  paper: {
+    padding: '8px 10px',
+    minWidth: 180,
+  },
+  tabIcon: {
+    marginLeft: 4,
+    marginRight: 4,
+    display: 'flex',
+  },
+  emptyTabIcon: {
+    marginLeft: 4,
+    marginRight: 4,
+    height: 20,
+    width: 24,
+    display: 'flex',
+  },
+  tooltip: {
+    zIndex: 3,
+    maxWidth: 'min(90%, 300px)',
+  },
+};
+
+type Props = {|
+  anchorElement: HTMLElement,
+  editorTab: EditorTab,
+|};
+
+const Tooltip = ({ anchorElement, editorTab }: Props) => {
+  const gdevelopTheme = React.useContext(GDevelopThemeContext);
+  const [tooltipStyle, setTooltipStyle] = React.useState<Object>(
+    styles.tooltip
+  );
+
+  const brightness = gdevelopTheme.palette.type === 'dark' ? 0.978 : 0.224;
+
+  const editorTabMetadata = getEditorTabMetadata(editorTab);
+
+  React.useEffect(
+    () => {
+      const timeoutId = setTimeout(() => {
+        setTooltipStyle(currentStyle => ({
+          ...currentStyle,
+          transition: 'transform 150ms ease-in-out',
+        }));
+      }, 100);
+      return () => clearTimeout(timeoutId);
+    },
+    // Apply transition after first display of tooltip to avoid having the
+    // transition weirdly applied from the 0;0 coordinates.
+    []
+  );
+
+  const title = editorKindToLabel[editorTabMetadata.editorKind];
+  const subtitle = [
+    'layout',
+    'layout events',
+    'events functions extension',
+  ].includes(editorTabMetadata.editorKind)
+    ? editorTabMetadata.projectItemName
+    : editorTabMetadata.editorKind === 'custom object' &&
+      editorTabMetadata.projectItemName
+    ? editorTabMetadata.projectItemName.split('::')[1]
path: newIDE/app/src/MainFrame/TabsTitlebar.js
user: login: ClementPasteau
body: just to be safe, we should probably check the length of the array resulting the split to ensure there are at least 2 elements.
Maybe, this should be in a React.useMemo() to make that logic easier to ready with `if`s ?
created_at: 2025-03-20T16:21:58Z
author_association: COLLABORATOR
diff_hunk: @@ -53,6 +205,39 @@ export default function TabsTitlebar({
     [backgroundColor]
   );
 
+  const onHoverEditorTab = React.useCallback(
+    (editorTab: ?EditorTab) => {
+      if (isTouchscreen) {
+        setTooltipData(null);
+        return;
+      }
+
+      if (tooltipTimeoutId.current) {
+        clearTimeout(tooltipTimeoutId.current);
path: newIDE/app/src/MainFrame/TabsTitlebar.js
user: login: ClementPasteau
body: should that timeout be cleared when the component unmounts? (it shouldn't unmount technically, but...)
created_at: 2025-03-20T16:23:20Z
author_association: COLLABORATOR
diff_hunk: @@ -53,6 +205,39 @@ export default function TabsTitlebar({
     [backgroundColor]
   );
 
+  const onHoverEditorTab = React.useCallback(
+    (editorTab: ?EditorTab) => {
+      if (isTouchscreen) {
+        setTooltipData(null);
+        return;
+      }
+
+      if (tooltipTimeoutId.current) {
+        clearTimeout(tooltipTimeoutId.current);
path: newIDE/app/src/MainFrame/TabsTitlebar.js
user: login: AlexandreSi
body: Good idea
created_at: 2025-03-20T16:40:38Z
author_association: CONTRIBUTOR
diff_hunk: @@ -27,24 +55,148 @@ const styles = {
     width: 34,
     height: 34,
   },
+  paper: {
+    padding: '8px 10px',
+    minWidth: 180,
+  },
+  tabIcon: {
+    marginLeft: 4,
+    marginRight: 4,
+    display: 'flex',
+  },
+  emptyTabIcon: {
+    marginLeft: 4,
+    marginRight: 4,
+    height: 20,
+    width: 24,
+    display: 'flex',
+  },
+  tooltip: {
+    zIndex: 3,
+    maxWidth: 'min(90%, 300px)',
+  },
+};
+
+type Props = {|
+  anchorElement: HTMLElement,
+  editorTab: EditorTab,
+|};
+
+const Tooltip = ({ anchorElement, editorTab }: Props) => {
+  const gdevelopTheme = React.useContext(GDevelopThemeContext);
+  const [tooltipStyle, setTooltipStyle] = React.useState<Object>(
+    styles.tooltip
+  );
+
+  const brightness = gdevelopTheme.palette.type === 'dark' ? 0.978 : 0.224;
+
+  const editorTabMetadata = getEditorTabMetadata(editorTab);
+
+  React.useEffect(
+    () => {
+      const timeoutId = setTimeout(() => {
+        setTooltipStyle(currentStyle => ({
+          ...currentStyle,
+          transition: 'transform 150ms ease-in-out',
+        }));
+      }, 100);
+      return () => clearTimeout(timeoutId);
+    },
+    // Apply transition after first display of tooltip to avoid having the
+    // transition weirdly applied from the 0;0 coordinates.
+    []
+  );
+
+  const title = editorKindToLabel[editorTabMetadata.editorKind];
+  const subtitle = [
+    'layout',
+    'layout events',
+    'events functions extension',
+  ].includes(editorTabMetadata.editorKind)
+    ? editorTabMetadata.projectItemName
+    : editorTabMetadata.editorKind === 'custom object' &&
+      editorTabMetadata.projectItemName
+    ? editorTabMetadata.projectItemName.split('::')[1]
path: newIDE/app/src/MainFrame/TabsTitlebar.js
user: login: AlexandreSi
body: I don't think using useMemo is interesting here but I simplified the logic
created_at: 2025-03-20T16:41:10Z
author_association: CONTRIBUTOR

PR comments:
user: login: ClementPasteau
created_at: 2025-03-20T16:25:12Z
body: ![image](https://github.com/user-attachments/assets/33ce9173-fd1f-427c-bf79-789d7c654ccb)

we can't see the name of the external layout ?
author_association: COLLABORATOR
user: login: AlexandreSi
created_at: 2025-03-20T16:42:13Z
body: > we can't see the name of the external layout ?

I added it thanks 


author_association: CONTRIBUTOR