Revert "chore: Move IDE header to ADS/Templates (#37406)" (#37739)

This reverts commit 8cb0beeecc.



<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->

## Summary by CodeRabbit

- **New Features**
- Introduced `HeaderEditorSwitcher` and `HeaderDropdown` components for
enhanced UI interactions.
- **Bug Fixes**
- Updated import paths for `IDE_HEADER_HEIGHT` across multiple
components to ensure consistent styling.
- **Refactor**
- Renamed and restructured components for clarity and improved
functionality, including `HeaderTitle` and `IDEHeaderSwitcher`.
- **Tests**
- Added tests for the `HeaderDropdown` component to validate rendering
and functionality.
- **Chores**
	- Removed deprecated components and files to streamline the codebase.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
This commit is contained in:
Hetu Nandu 2024-11-26 18:06:40 +05:30 committed by GitHub
parent 7ac4df21dd
commit 1ea45e8e48
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
32 changed files with 311 additions and 527 deletions

View File

@ -1,38 +0,0 @@
import React from "react";
import { ListHeaderContainer } from "./styles";
import { Text } from "../../Text";
import { Flex } from "../../Flex";
interface Props {
headerText: string;
headerControls?: React.ReactNode;
maxHeight?: string;
headerClassName?: string;
children: React.ReactNode | React.ReactNode[];
}
export const ListWithHeader = (props: Props) => {
return (
<Flex
flexDirection="column"
justifyContent="center"
maxHeight={props.maxHeight}
overflow="hidden"
>
<ListHeaderContainer className={props.headerClassName}>
<Text kind="heading-xs">{props.headerText}</Text>
{props.headerControls}
</ListHeaderContainer>
<Flex
alignItems="center"
flex="1"
flexDirection="column"
overflow="auto"
px="spaces-2"
width="100%"
>
{props.children}
</Flex>
</Flex>
);
};

View File

@ -1,2 +0,0 @@
export { ListItemContainer, ListHeaderContainer } from "./styles";
export { ListWithHeader } from "./ListWithHeader";

View File

@ -1,27 +0,0 @@
import styled from "styled-components";
export const ListItemContainer = styled.div`
width: 100%;
& .t--entity-item {
grid-template-columns: 0 auto 1fr auto auto auto auto auto;
height: 32px;
& .t--entity-name {
padding-left: var(--ads-v2-spaces-3);
}
}
`;
export const ListHeaderContainer = styled.div`
padding: var(--ads-v2-spaces-3);
padding-right: var(--ads-v2-spaces-2);
display: flex;
justify-content: space-between;
align-items: center;
height: 40px;
span {
line-height: 20px;
}
`;

View File

@ -1,20 +0,0 @@
import styled from "styled-components";
import { PopoverContent } from "../../../Popover";
export const SwitchTrigger = styled.div<{ active: boolean }>`
display: flex;
border-radius: var(--ads-v2-border-radius);
background-color: ${(props) =>
props.active ? `var(--ads-v2-color-bg-subtle)` : "unset"};
cursor: pointer;
padding: var(--ads-v2-spaces-2);
:hover {
background-color: var(--ads-v2-color-bg-subtle);
}
`;
export const ContentContainer = styled(PopoverContent)`
padding: 0;
padding-bottom: 0.25em;
`;

View File

@ -1,88 +0,0 @@
import React, { type ForwardedRef, useCallback } from "react";
import { Flex } from "../../../Flex";
import { Icon } from "../../../Icon";
import { Popover, PopoverTrigger } from "../../../Popover";
import { Text } from "../../../Text";
import * as Styled from "./HeaderSwitcher.styles";
interface Props {
prefix: string;
title?: string;
titleTestId: string;
active: boolean;
setActive: (active: boolean) => void;
onClick?: React.MouseEventHandler<HTMLDivElement>;
className?: string;
children: React.ReactNode;
}
export const IDEHeaderSwitcher = React.forwardRef(
(props: Props, ref: ForwardedRef<HTMLDivElement>) => {
const {
active,
children,
className,
onClick,
prefix,
setActive,
title,
titleTestId,
...rest
} = props;
const separator = title ? " /" : "";
const closeSwitcher = useCallback(() => {
return setActive(false);
}, [setActive]);
return (
<Popover onOpenChange={setActive} open={active}>
<PopoverTrigger>
<Styled.SwitchTrigger
active={active}
className={`flex align-center items-center justify-center ${className}`}
data-testid={titleTestId}
onClick={onClick}
ref={ref}
{...rest}
>
<Text
color="var(--ads-v2-colors-content-label-inactive-fg)"
kind="body-m"
>
{prefix + separator}
</Text>
<Flex
alignItems="center"
className={titleTestId}
data-active={active}
gap="spaces-1"
height="100%"
justifyContent="center"
paddingLeft="spaces-2"
>
<Text isBold kind="body-m">
{title}
</Text>
<Icon
color={
title
? undefined
: "var(--ads-v2-colors-content-label-inactive-fg)"
}
name={active ? "arrow-up-s-line" : "arrow-down-s-line"}
size="md"
/>
</Flex>
</Styled.SwitchTrigger>
</PopoverTrigger>
<Styled.ContentContainer align="start" onEscapeKeyDown={closeSwitcher}>
{children}
</Styled.ContentContainer>
</Popover>
);
},
);
IDEHeaderSwitcher.displayName = "IDEHeaderSwitcher";

View File

@ -1 +0,0 @@
export { IDEHeaderSwitcher } from "./IDEHeaderSwitcher";

View File

@ -1,2 +0,0 @@
export const IDE_HEADER_HEIGHT = 40;
export const LOGO_WIDTH = 50;

View File

@ -1,28 +0,0 @@
import { Canvas, Meta } from "@storybook/blocks";
import * as IDEHeaderStories from "./IDEHeader.stories";
<Meta of={IDEHeaderStories} />
# IDEHeader
IDEHeader sets the stage for the IDE experience. It is the topmost section of the IDE that contains the Appsmith logo, the app name, and the user profile.
<Canvas of={IDEHeaderStories.Default} />
## Anatomy
### Left Section options
The local title
#### Header Title
A title that is specific to the app state. It is displayed on the left side of the header.
<Canvas of={IDEHeaderStories.WithHeaderTitle} />
#### Header Dropdown
A dropdown that allows the user to switch between different pages.
<Canvas of={IDEHeaderStories.WithHeaderDropdown} />

View File

@ -1,109 +0,0 @@
import React from "react";
import type { Meta } from "@storybook/react";
import { IDEHeader } from "./IDEHeader";
import { IDEHeaderTitle } from "./IDEHeaderTitle";
import { IDEHeaderSwitcher } from "./HeaderSwitcher";
import { noop } from "lodash";
import { Icon } from "../../Icon";
import { Button } from "../../Button";
import { List } from "../../List";
import { Flex } from "../../Flex";
import { Text } from "../../Text";
import { ListHeaderContainer } from "../EntityExplorer/styles";
const meta: Meta = {
title: "ADS/Templates/IDEHeader",
component: IDEHeader,
parameters: {
layout: "fullscreen",
},
decorators: [
(Story: () => React.ReactNode) => (
<div style={{ width: "100%" }}>{Story()}</div>
),
],
};
export default meta;
export const Default = () => (
<IDEHeader>
<IDEHeader.Left logo={<Icon name="upload-cloud" size="md" />}>
<span>Left Content</span>
</IDEHeader.Left>
<IDEHeader.Center>
<span>Center Content</span>
</IDEHeader.Center>
<IDEHeader.Right>
<span>Right Content</span>
</IDEHeader.Right>
</IDEHeader>
);
export const WithHeaderTitle = () => {
return (
<IDEHeader>
<IDEHeader.Left logo={<Icon name="upload-cloud" size="md" />}>
<IDEHeaderTitle title="Settings" />
</IDEHeader.Left>
<IDEHeader.Center>
<div />
</IDEHeader.Center>
<IDEHeader.Right>
<div />
</IDEHeader.Right>
</IDEHeader>
);
};
export const WithHeaderDropdown = () => {
const [open, setOpen] = React.useState(false);
return (
<IDEHeader>
<IDEHeader.Left logo={<Icon name="upload-cloud" size="md" />}>
<IDEHeaderSwitcher
active={open}
prefix={"Pages"}
setActive={setOpen}
title="Page1"
titleTestId={"testId"}
>
<Flex
flexDirection="column"
justifyContent="center"
maxHeight={"300px"}
overflow="hidden"
>
<ListHeaderContainer>
<Text kind="heading-xs">Pages</Text>
<Button isIconButton kind="tertiary" startIcon="plus" />
</ListHeaderContainer>
<List
items={[
{
title: "Page1",
onClick: noop,
description: "",
descriptionType: "inline",
},
{
title: "Page2",
onClick: noop,
description: "",
descriptionType: "inline",
},
]}
/>
</Flex>
</IDEHeaderSwitcher>
</IDEHeader.Left>
<IDEHeader.Center>
<div />
</IDEHeader.Center>
<IDEHeader.Right>
<div />
</IDEHeader.Right>
</IDEHeader>
);
};

View File

@ -1,80 +0,0 @@
import React, { type PropsWithChildren } from "react";
import { Flex } from "../../Flex";
import { IDE_HEADER_HEIGHT, LOGO_WIDTH } from "./IDEHeader.constants";
interface ChildrenProps {
children: React.ReactNode | React.ReactNode[];
}
const Left = (props: PropsWithChildren<{ logo: React.ReactNode }>) => {
return (
<Flex
alignItems="center"
className="header-left-section"
flex="1"
gap="spaces-4"
height="100%"
justifyContent="left"
>
<Flex
alignItems="center"
borderRight="1px solid var(--ads-v2-color-border)"
h="100%"
justifyContent="center"
w={`${LOGO_WIDTH}px`}
>
{props.logo}
</Flex>
{props.children}
</Flex>
);
};
const Center = (props: ChildrenProps) => {
return (
<Flex
alignItems="center"
className="header-center-section"
flex="1"
height="100%"
justifyContent="center"
>
{props.children}
</Flex>
);
};
const Right = (props: ChildrenProps) => {
return (
<Flex
alignItems="center"
className="header-right-section"
flex="1"
gap="spaces-3"
height="100%"
justifyContent="right"
>
{props.children}
</Flex>
);
};
export const IDEHeader = (props: ChildrenProps) => {
return (
<Flex
alignItems="center"
background="var(--ads-v2-color-bg)"
borderBottom="1px solid var(--ads-v2-color-border)"
className="t--editor-header"
height={`${IDE_HEADER_HEIGHT}px`}
overflow="hidden"
width="100%"
>
{props.children}
</Flex>
);
};
IDEHeader.Left = Left;
IDEHeader.Center = Center;
IDEHeader.Right = Right;

View File

@ -1,19 +0,0 @@
/**
* The IDEHeader gets exported with 3 layout subsections.
* IDEHeader.Left, IDEHeader.Center, IDEHeader.Right
* These are composable components that you can use to spread the content of the header
* It is possible to use the IDE Header without using these subsections
*/
export { IDEHeader } from "./IDEHeader";
export { IDE_HEADER_HEIGHT, LOGO_WIDTH } from "./IDEHeader.constants";
/**
* IDEHeaderSwitcher can be used for a trigger component to show a dropdown for pages, modules
* or any list of elements in the header E.g., Pages / Page 1
*/
export { IDEHeaderSwitcher } from "./HeaderSwitcher";
/**
* IDEHeaderTitle is a small text styled wrapper that is suitable to be used inside IDEHeader
*/
export { IDEHeaderTitle } from "./IDEHeaderTitle";

View File

@ -1,2 +0,0 @@
export * from "./IDEHeader";
export * from "./EntityExplorer";

View File

@ -36,4 +36,3 @@ export * from "./Toast";
export * from "./ToggleButton";
export * from "./Tooltip";
export * from "./ToggleButtonGroup";
export * from "./Templates";

View File

@ -0,0 +1,40 @@
import React from "react";
import { render } from "@testing-library/react";
import HeaderDropdown from "./HeaderDropdown";
import "@testing-library/jest-dom";
describe("HeaderDropdown", () => {
it("renders children components correctly", () => {
const { getByText } = render(
<HeaderDropdown>
<HeaderDropdown.Header>
<span>Header</span>
</HeaderDropdown.Header>
<HeaderDropdown.Body>
<span>Body</span>
</HeaderDropdown.Body>
</HeaderDropdown>,
);
expect(getByText("Header")).toBeInTheDocument();
expect(getByText("Body")).toBeInTheDocument();
});
it("applies custom className to the header", () => {
const customClass = "my-custom-class";
const { container } = render(
<HeaderDropdown>
<HeaderDropdown.Header className={customClass}>
<span>Header</span>
</HeaderDropdown.Header>
<HeaderDropdown.Body>
<span>Body</span>
</HeaderDropdown.Body>
</HeaderDropdown>,
);
const headerElement = container.querySelector(`.${customClass}`);
expect(headerElement).toBeInTheDocument();
});
});

View File

@ -0,0 +1,79 @@
import { Flex } from "@appsmith/ads";
import React from "react";
import styled from "styled-components";
const Container = styled(Flex)`
& .t--entity-item {
grid-template-columns: 0 auto 1fr auto auto auto auto auto;
height: 32px;
& .t--entity-name {
padding-left: var(--ads-v2-spaces-3);
}
}
`;
const HeaderWrapper = styled.div`
padding: var(--ads-v2-spaces-3);
padding-right: var(--ads-v2-spaces-2);
display: flex;
justify-content: space-between;
align-items: center;
height: 40px;
span {
line-height: 20px;
}
`;
interface EditorHeaderDropdown {
children: React.ReactNode;
}
interface EditorHeaderDropdownHeader {
children: React.ReactNode;
className?: string;
}
interface EditorHeaderDropdownBody {
children: React.ReactNode;
}
function EditorHeaderDropdown({ children }: EditorHeaderDropdown) {
return (
<Container
flexDirection="column"
justifyContent="center"
maxHeight="300px"
overflow="hidden"
>
{children}
</Container>
);
}
function EditorHeaderDropdownHeader({
children,
className,
}: EditorHeaderDropdownHeader) {
return <HeaderWrapper className={className}>{children}</HeaderWrapper>;
}
function EditorHeaderDropdownBody({ children }: EditorHeaderDropdownBody) {
return (
<Flex
alignItems="center"
flex="1"
flexDirection="column"
overflow="auto"
px="spaces-2"
width="100%"
>
{children}
</Flex>
);
}
EditorHeaderDropdown.Header = EditorHeaderDropdownHeader;
EditorHeaderDropdown.Body = EditorHeaderDropdownBody;
export default EditorHeaderDropdown;

View File

@ -1,67 +1,67 @@
import React from "react";
import { render, fireEvent, screen } from "@testing-library/react";
import { IDEHeaderSwitcher } from "./IDEHeaderSwitcher";
import { render, fireEvent } from "@testing-library/react";
import HeaderEditorSwitcher from "./HeaderEditorSwitcher";
import "@testing-library/jest-dom";
describe("HeaderSwitcher", () => {
describe("HeaderEditorSwitcher", () => {
const mockOnClick = jest.fn();
const mockSetActive = jest.fn();
const defaultProps = {
prefix: "Prefix",
title: "Title",
titleTestId: "titleTestId",
active: false,
onClick: mockOnClick,
setActive: mockSetActive,
children: <span>Test</span>,
};
it("renders with correct props", () => {
render(<IDEHeaderSwitcher {...defaultProps} />);
const { getByText } = render(<HeaderEditorSwitcher {...defaultProps} />);
// eslint-disable-next-line testing-library/no-node-access
const testIdElement = document.getElementsByClassName(
defaultProps.titleTestId,
);
expect(screen.getByText("Prefix /")).toBeInTheDocument();
expect(screen.getByText(defaultProps.title)).toBeInTheDocument();
expect(getByText("Prefix /")).toBeInTheDocument();
expect(getByText(defaultProps.title)).toBeInTheDocument();
expect(testIdElement).toBeDefined();
});
it("renders active state correctly", () => {
render(<IDEHeaderSwitcher {...defaultProps} active />);
const { getByText } = render(
<HeaderEditorSwitcher {...defaultProps} active />,
);
expect(screen.getByText("Prefix /")).toHaveStyle(
expect(getByText("Prefix /")).toHaveStyle(
"background-color: var(--ads-v2-color-bg-subtle)",
);
});
it("calls onClick handler when clicked", () => {
render(<IDEHeaderSwitcher {...defaultProps} />);
const { getByText } = render(<HeaderEditorSwitcher {...defaultProps} />);
fireEvent.click(screen.getByText("Title"));
fireEvent.click(getByText("Title"));
expect(mockOnClick).toHaveBeenCalled();
});
it("forwards ref correctly", () => {
const ref = React.createRef<HTMLDivElement>();
const ref = React.createRef();
render(<IDEHeaderSwitcher {...defaultProps} ref={ref} />);
render(<HeaderEditorSwitcher {...defaultProps} ref={ref} />);
expect(ref.current).toBeTruthy();
});
it("does not crash when onClick is not provided", () => {
render(<IDEHeaderSwitcher {...defaultProps} onClick={undefined} />);
const { getByText } = render(
<HeaderEditorSwitcher {...defaultProps} onClick={undefined} />,
);
fireEvent.click(screen.getByText("Title")); // Should not throw error
fireEvent.click(getByText("Title")); // Should not throw error
});
it("does not show separator and applies different inactive color to icon", () => {
const ref = React.createRef<HTMLDivElement>();
const { container } = render(
<IDEHeaderSwitcher
const ref = React.createRef();
const { container, getByTestId } = render(
<HeaderEditorSwitcher
{...defaultProps}
data-testid="root-div"
ref={ref}
@ -69,10 +69,9 @@ describe("HeaderSwitcher", () => {
/>,
);
// eslint-disable-next-line testing-library/no-container,testing-library/no-node-access
const icon = container.querySelector(".remixicon-icon"); // Get chevron icon
expect(screen.getByTestId("root-div")).toHaveTextContent("Prefix");
expect(getByTestId("root-div")).toHaveTextContent("Prefix");
expect(icon).toHaveAttribute(
"fill",
"var(--ads-v2-colors-content-label-inactive-fg)",
@ -84,20 +83,16 @@ describe("HeaderSwitcher", () => {
const className = "custom-class";
const { container } = render(
<IDEHeaderSwitcher
<HeaderEditorSwitcher
active
className={className}
data-testid={testId} // Additional prop
prefix="Prefix"
setActive={mockSetActive}
title="Title"
titleTestId="titleTestId"
>
<span>Test</span>
</IDEHeaderSwitcher>,
/>,
);
// eslint-disable-next-line testing-library/no-container,testing-library/no-node-access
const firstDiv = container.querySelector("div"); // Get the first div element
const classNames = firstDiv?.getAttribute("class")?.split(" ") || [];

View File

@ -0,0 +1,73 @@
import { Flex, Icon, Text } from "@appsmith/ads";
import React from "react";
import styled from "styled-components";
interface HeaderEditorSwitcherProps {
prefix: string;
title?: string;
titleTestId: string;
active: boolean;
onClick?: React.MouseEventHandler<HTMLDivElement>;
className?: string;
}
const SwitchTrigger = styled.div<{ active: boolean }>`
border-radius: var(--ads-v2-border-radius);
background-color: ${(props) =>
props.active ? `var(--ads-v2-color-bg-subtle)` : "unset"};
cursor: pointer;
padding: var(--ads-v2-spaces-2);
:hover {
background-color: var(--ads-v2-color-bg-subtle);
}
`;
const HeaderEditorSwitcher = React.forwardRef(
// TODO: Fix this the next time the file is edited
// eslint-disable-next-line @typescript-eslint/no-explicit-any
(props: HeaderEditorSwitcherProps, ref: any) => {
const { active, className, prefix, title, titleTestId, ...rest } = props;
const separator = title ? " /" : "";
return (
<SwitchTrigger
active={active}
className={`flex align-center items-center justify-center ${className}`}
ref={ref}
{...rest}
>
<Text
color="var(--ads-v2-colors-content-label-inactive-fg)"
kind="body-m"
>
{prefix + separator}
</Text>
<Flex
alignItems="center"
className={titleTestId}
data-active={active}
gap="spaces-1"
height="100%"
justifyContent="center"
paddingLeft="spaces-2"
>
<Text isBold kind="body-m">
{title}
</Text>
<Icon
color={
title
? undefined
: "var(--ads-v2-colors-content-label-inactive-fg)"
}
name={active ? "arrow-up-s-line" : "arrow-down-s-line"}
size="md"
/>
</Flex>
</SwitchTrigger>
);
},
);
export default HeaderEditorSwitcher;

View File

@ -1,11 +1,10 @@
import React from "react";
import { Flex } from "../../Flex";
import { Text } from "../../Text";
import { Flex, Text } from "@appsmith/ads";
/**
* Handy little styled component that can be used to render the title in the IDEHeader component
* **/
export const IDEHeaderTitle = ({ title }: { title: string }) => {
const HeaderTitle = ({ title }: { title: string }) => {
return (
<Flex alignItems="center" height="100%" justifyContent="center">
<Text isBold kind="body-m">
@ -14,3 +13,5 @@ export const IDEHeaderTitle = ({ title }: { title: string }) => {
</Flex>
);
};
export default HeaderTitle;

View File

@ -3,6 +3,15 @@
Components that are part of the main structure of the IDE experience
====================================================**/
/**
* The IDEHeader gets exported with 3 layout subsections.
* IDEHeader.Left, IDEHeader.Center, IDEHeader.Right
* These are composable components that you can use to spread the content of the header
* It is possible to use the IDE Header without using these subsections
*/
export { IDE_HEADER_HEIGHT } from "./Structure/constants";
export { default as IDEHeader } from "./Structure/Header";
/**
* The IDEToolbar gets exported with 2 layout subsections.
* IDEToolbar.Left and IDEToolbar.Right
@ -16,6 +25,22 @@ export { default as IDEToolbar } from "./Structure/Toolbar";
Components that are smaller UI abstractions for easy use and standardisation within the IDE
=======================================================**/
/**
* IDEHeaderTitle is a small text styled wrapper that is suitable to be used inside IDEHeader
*/
export { default as IDEHeaderTitle } from "./Components/HeaderTitle";
/**
* IDEHeaderEditorSwitcher can be used for a trigger component to show a dropdown for pages, modules
* or any list of elements in the header E.g., Pages / Page 1
*/
export { default as IDEHeaderEditorSwitcher } from "./Components/HeaderEditorSwitcher";
/**
* The IDEHeaderDropdown gets exported with 2 layout subsections.
* IDEHeaderDropdown.Header, IDEHeaderDropdown.Body
* These are composable components that you can use to spread the content of the header
* It is possible to use the IDE Header without using these subsections
*/
export { default as IDEHeaderDropdown } from "./Components/HeaderDropdown";
/**
* IDEBottomView is a versatile view meant to be at the bottom of the screen.
* It is resizable and can be hidden or collapsed based on the behavior configured

View File

@ -2485,8 +2485,7 @@ export const ADD_PAGE_FROM_TEMPLATE_MODAL = {
export const HEADER_TITLES = {
DATA: () => "Data",
PAGES: () => "Pages",
EDITOR: () => "Editor",
EDITOR: () => "Pages",
SETTINGS: () => "Settings",
LIBRARIES: () => "Libraries",
};

View File

@ -1,6 +1,7 @@
import styled from "styled-components";
import { Classes } from "@blueprintjs/core";
import { Link, Text, IDE_HEADER_HEIGHT } from "@appsmith/ads";
import { Link, Text } from "@appsmith/ads";
import { IDE_HEADER_HEIGHT } from "IDE";
export const Wrapper = styled.div`
flex-basis: calc(100% - ${(props) => props.theme.homePage.leftPane.width}px);

View File

@ -34,7 +34,7 @@ import { getIsAppSettingsPaneWithNavigationTabOpen } from "selectors/appSettings
import NavigationLogo from "ee/pages/AppViewer/NavigationLogo";
import MenuItemContainer from "./components/MenuItemContainer";
import BackToAppsButton from "./components/BackToAppsButton";
import { IDE_HEADER_HEIGHT } from "@appsmith/ads";
import { IDE_HEADER_HEIGHT } from "IDE";
import { BOTTOM_BAR_HEIGHT } from "components/BottomBar/constants";
interface SidebarProps {

View File

@ -1,7 +1,8 @@
import styled from "styled-components";
import { Classes } from "@blueprintjs/core";
import { getTypographyByKey } from "@appsmith/ads-old";
import { Icon, IDE_HEADER_HEIGHT } from "@appsmith/ads";
import { Icon } from "@appsmith/ads";
import { IDE_HEADER_HEIGHT } from "IDE";
export const Container = styled.div`
display: flex;

View File

@ -1,5 +1,5 @@
import React, { useCallback, useMemo, useState } from "react";
import { ListItemContainer, ListWithHeader } from "@appsmith/ads";
import { Text } from "@appsmith/ads";
import { useDispatch, useSelector } from "react-redux";
import { useLocation } from "react-router";
@ -18,6 +18,7 @@ import { getNextEntityName } from "utils/AppsmithUtils";
import { getCurrentWorkspaceId } from "ee/selectors/selectedWorkspaceSelectors";
import { getInstanceId } from "ee/selectors/tenantSelectors";
import { PageElement } from "pages/Editor/IDE/EditorPane/components/PageElement";
import { IDEHeaderDropdown } from "IDE";
import { PAGE_ENTITY_NAME } from "ee/constants/messages";
const PagesSection = ({ onItemSelected }: { onItemSelected: () => void }) => {
@ -49,50 +50,35 @@ const PagesSection = ({ onItemSelected }: { onItemSelected: () => void }) => {
dispatch(
createNewPageFromEntities(applicationId, name, workspaceId, instanceId),
);
}, [pages, dispatch, applicationId, workspaceId, instanceId]);
}, [dispatch, pages, applicationId]);
const onMenuClose = useCallback(() => setIsMenuOpen(false), [setIsMenuOpen]);
const pageElements = useMemo(
() =>
pages.map((page) => (
<ListItemContainer key={page.pageId}>
<PageElement onClick={onItemSelected} page={page} />
</ListItemContainer>
<PageElement key={page.pageId} onClick={onItemSelected} page={page} />
)),
[pages, location.pathname, onItemSelected],
[pages, location.pathname],
);
const createPageContextMenu = useMemo(() => {
if (!canCreatePages) return null;
return (
<AddPageContextMenu
buttonSize="sm"
className={`${EntityClassNames.ADD_BUTTON} group pages`}
createPageCallback={createPageCallback}
onItemSelected={onItemSelected}
onMenuClose={onMenuClose}
openMenu={isMenuOpen}
/>
);
}, [
canCreatePages,
createPageCallback,
isMenuOpen,
onItemSelected,
onMenuClose,
]);
return (
<ListWithHeader
headerClassName={"pages"}
headerControls={createPageContextMenu}
headerText={`All Pages (${pages.length})`}
maxHeight={"300px"}
>
{pageElements}
</ListWithHeader>
<IDEHeaderDropdown>
<IDEHeaderDropdown.Header className="pages">
<Text kind="heading-xs">{`All Pages (${pages.length})`}</Text>
{canCreatePages ? (
<AddPageContextMenu
buttonSize="sm"
className={`${EntityClassNames.ADD_BUTTON} group pages`}
createPageCallback={createPageCallback}
onItemSelected={onItemSelected}
onMenuClose={onMenuClose}
openMenu={isMenuOpen}
/>
) : null}
</IDEHeaderDropdown.Header>
<IDEHeaderDropdown.Body>{pageElements}</IDEHeaderDropdown.Body>
</IDEHeaderDropdown>
);
};

View File

@ -1,34 +1,35 @@
import React from "react";
import { IDEHeaderSwitcher } from "@appsmith/ads";
import React, { useState } from "react";
import { Popover, PopoverTrigger, PopoverContent } from "@appsmith/ads";
import { createMessage, HEADER_TITLES } from "ee/constants/messages";
import { PagesSection } from "../EditorPane/PagesSection";
import { useBoolean } from "usehooks-ts";
import { useSelector } from "react-redux";
import { getCurrentPageId, getPageById } from "selectors/editorSelectors";
import { IDEHeaderEditorSwitcher } from "IDE";
const EditorTitle = () => {
const {
setFalse: setMenuClose,
setValue: setMenuState,
value: isMenuOpen,
} = useBoolean(false);
const EditorTitle = ({ title }: { title: string }) => {
const [active, setActive] = useState(false);
const pageId = useSelector(getCurrentPageId) as string;
const currentPage = useSelector(getPageById(pageId));
if (!currentPage) return null;
const closeMenu = () => {
setActive(false);
};
return (
<IDEHeaderSwitcher
active={isMenuOpen}
prefix={createMessage(HEADER_TITLES.PAGES)}
setActive={setMenuState}
title={currentPage.pageName}
titleTestId="t--pages-switcher"
>
<PagesSection onItemSelected={setMenuClose} />
</IDEHeaderSwitcher>
<Popover onOpenChange={setActive} open={active}>
<PopoverTrigger>
<IDEHeaderEditorSwitcher
active={active}
prefix={createMessage(HEADER_TITLES.EDITOR)}
title={title}
titleTestId="t--pages-switcher"
/>
</PopoverTrigger>
<PopoverContent
align="start"
className="!p-0 !pb-1"
onEscapeKeyDown={closeMenu}
>
<PagesSection onItemSelected={closeMenu} />
</PopoverContent>
</Popover>
);
};

View File

@ -13,8 +13,6 @@ import {
TabPanel,
Button,
Link,
IDEHeader,
IDEHeaderTitle,
} from "@appsmith/ads";
import { useDispatch, useSelector } from "react-redux";
import { EditInteractionKind, SavingState } from "@appsmith/ads-old";
@ -75,10 +73,11 @@ import { EditorTitle } from "./EditorTitle";
import { useCurrentAppState } from "../hooks/useCurrentAppState";
import { EditorState } from "ee/entities/IDE/constants";
import { EditorSaveIndicator } from "pages/Editor/EditorSaveIndicator";
import type { Page } from "entities/Page";
import { IDEHeader, IDEHeaderTitle } from "IDE";
import { APPLICATIONS_URL } from "constants/routes";
import { useNavigationMenuData } from "../../EditorName/useNavigationMenuData";
import useLibraryHeaderTitle from "ee/pages/Editor/IDE/Header/useLibraryHeaderTitle";
import { AppsmithLink } from "pages/Editor/AppsmithLink";
const StyledDivider = styled(Divider)`
height: 50%;
@ -90,9 +89,10 @@ const { cloudHosting } = getAppsmithConfigs();
interface HeaderTitleProps {
appState: EditorState;
currentPage?: Page;
}
const HeaderTitleComponent = ({ appState }: HeaderTitleProps) => {
const HeaderTitleComponent = ({ appState, currentPage }: HeaderTitleProps) => {
const libraryHeaderTitle = useLibraryHeaderTitle();
switch (appState) {
@ -104,7 +104,7 @@ const HeaderTitleComponent = ({ appState }: HeaderTitleProps) => {
/>
);
case EditorState.EDITOR:
return <EditorTitle key={appState} />;
return <EditorTitle key={appState} title={currentPage?.pageName || ""} />;
case EditorState.SETTINGS:
return (
<IDEHeaderTitle
@ -115,7 +115,7 @@ const HeaderTitleComponent = ({ appState }: HeaderTitleProps) => {
case EditorState.LIBRARIES:
return <IDEHeaderTitle key={appState} title={libraryHeaderTitle} />;
default:
return <EditorTitle key={appState} />;
return <EditorTitle key={appState} title={currentPage?.pageName || ""} />;
}
};
@ -218,8 +218,8 @@ const Header = () => {
return (
<>
<IDEHeader>
<IDEHeader.Left logo={<AppsmithLink />}>
<HeaderTitleComponent appState={appState} />
<IDEHeader.Left>
<HeaderTitleComponent appState={appState} currentPage={currentPage} />
<EditorSaveIndicator isSaving={isSaving} saveError={pageSaveError} />
</IDEHeader.Left>
<IDEHeader.Center>

View File

@ -6,7 +6,7 @@ import {
RUN_GUTTER_ID,
} from "./constants";
import { thinScrollbar } from "constants/DefaultTheme";
import { IDE_HEADER_HEIGHT } from "@appsmith/ads";
import { IDE_HEADER_HEIGHT } from "IDE";
export const CodeEditorWithGutterStyles = css`
.${RUN_GUTTER_ID} {

View File

@ -8,7 +8,7 @@ import { getIsAppSettingsPaneWithNavigationTabOpen } from "selectors/appSettings
import { EditorState } from "ee/entities/IDE/constants";
import { RenderModes } from "constants/WidgetConstants";
import styled from "styled-components";
import { IDE_HEADER_HEIGHT } from "@appsmith/ads";
import { IDE_HEADER_HEIGHT } from "IDE";
import { BOTTOM_BAR_HEIGHT } from "components/BottomBar/constants";
const Container = styled.div`

View File

@ -1,7 +1,7 @@
import styled from "styled-components";
import { Profile } from "pages/common/ProfileImage";
import { getTypographyByKey } from "@appsmith/ads-old";
import { IDE_HEADER_HEIGHT } from "@appsmith/ads";
import { IDE_HEADER_HEIGHT } from "IDE";
export const HeaderWrapper = styled.div`
width: 100%;

View File

@ -4,7 +4,7 @@ import classNames from "classnames";
import { useSelector } from "react-redux";
import { combinedPreviewModeSelector } from "../../../selectors/editorSelectors";
import { protectedModeSelector } from "selectors/gitSyncSelectors";
import { IDE_HEADER_HEIGHT } from "@appsmith/ads";
import { IDE_HEADER_HEIGHT } from "../../../IDE";
import { BOTTOM_BAR_HEIGHT } from "../../../components/BottomBar/constants";
import { PROTECTED_CALLOUT_HEIGHT } from "../IDE/ProtectedCallout";

View File

@ -3,7 +3,7 @@ import styled from "styled-components";
import { snipingModeSelector } from "selectors/editorSelectors";
import { retryPromise } from "utils/AppsmithUtils";
import { useSelector } from "react-redux";
import { IDE_HEADER_HEIGHT } from "@appsmith/ads";
import { IDE_HEADER_HEIGHT } from "IDE";
const BindingBanner = styled.div`
position: fixed;

View File

@ -50,7 +50,7 @@ import { PartialExportModal } from "components/editorComponents/PartialImportExp
import { PartialImportModal } from "components/editorComponents/PartialImportExport/PartialImportModal";
import type { Page } from "entities/Page";
import { AppCURLImportModal } from "ee/pages/Editor/CurlImport";
import { IDE_HEADER_HEIGHT } from "@appsmith/ads";
import { IDE_HEADER_HEIGHT } from "IDE";
import GeneratePageModal from "./GeneratePage";
interface EditorProps {