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

## Description

Extracting out our IDE Header component into ADS for better usability. 

ADS Templates are shallow components built using opinionated ADS which
provide "slots" to place other business logic components inside it. It
reduces the work of using ADS by providing pre built UI components

Also creating the EntityExplorer folder and created ListWithHeader as a
component. Will keep updating this ADS template as we work on Entity
Explorer modularization


Fixes #37607

## Automation

/ok-to-test tags="@tag.All"

### 🔍 Cypress test results
<!-- This is an auto-generated comment: Cypress test results  -->
> [!TIP]
> 🟢 🟢 🟢 All cypress tests have passed! 🎉 🎉 🎉
> Workflow run:
<https://github.com/appsmithorg/appsmith/actions/runs/11971098012>
> Commit: e28c56e76aa47d727a7e9df0f46948e0785b49bf
> <a
href="https://internal.appsmith.com/app/cypress-dashboard/rundetails-65890b3c81d7400d08fa9ee5?branch=master&workflowId=11971098012&attempt=2"
target="_blank">Cypress dashboard</a>.
> Tags: `@tag.All`
> Spec:
> <hr>Fri, 22 Nov 2024 13:13:39 UTC
<!-- end of auto-generated comment: Cypress test results  -->


## Communication
Should the DevRel and Marketing teams inform users about this change?
- [ ] Yes
- [ ] No


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

## Release Notes

- **New Features**
- Introduced the `IDEHeader` component with subcomponents (`Left`,
`Center`, `Right`) for improved header layout.
	- Added `IDEHeaderSwitcher` for enhanced navigation within the header.
- New styled components `ListItemContainer`, `ListHeaderContainer`, and
`ListWithHeader` for the entity explorer interface.
- Added new constants `IDE_HEADER_HEIGHT` and `LOGO_WIDTH` for
standardized dimensions in the header layout.

- **Improvements**
- Updated header titles and constants for better clarity in the user
interface.
- Enhanced layout and styling of the `PagesSection` component for
improved user experience.
- Consolidated import statements for better organization across various
components.
	- Removed deprecated components and tests to streamline the codebase.

- **Documentation**
- Added documentation for the `IDEHeader` component, including stories
for visual representation.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
This commit is contained in:
Hetu Nandu 2024-11-26 12:44:11 +05:30 committed by GitHub
parent 5cfe143b86
commit 8cb0beeecc
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
32 changed files with 527 additions and 311 deletions

View File

@ -0,0 +1,38 @@
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

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

View File

@ -0,0 +1,27 @@
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

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

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

View File

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

View File

@ -0,0 +1,28 @@
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

@ -0,0 +1,109 @@
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

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

View File

@ -0,0 +1,19 @@
/**
* 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

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

View File

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

View File

@ -1,40 +0,0 @@
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

@ -1,79 +0,0 @@
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,73 +0,0 @@
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

@ -3,15 +3,6 @@
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
@ -25,22 +16,6 @@ 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,7 +2485,8 @@ export const ADD_PAGE_FROM_TEMPLATE_MODAL = {
export const HEADER_TITLES = {
DATA: () => "Data",
EDITOR: () => "Pages",
PAGES: () => "Pages",
EDITOR: () => "Editor",
SETTINGS: () => "Settings",
LIBRARIES: () => "Libraries",
};

View File

@ -1,7 +1,6 @@
import styled from "styled-components";
import { Classes } from "@blueprintjs/core";
import { Link, Text } from "@appsmith/ads";
import { IDE_HEADER_HEIGHT } from "IDE";
import { Link, Text, IDE_HEADER_HEIGHT } from "@appsmith/ads";
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 "IDE";
import { IDE_HEADER_HEIGHT } from "@appsmith/ads";
import { BOTTOM_BAR_HEIGHT } from "components/BottomBar/constants";
interface SidebarProps {

View File

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

View File

@ -1,5 +1,5 @@
import React, { useCallback, useMemo, useState } from "react";
import { Text } from "@appsmith/ads";
import { ListItemContainer, ListWithHeader } from "@appsmith/ads";
import { useDispatch, useSelector } from "react-redux";
import { useLocation } from "react-router";
@ -18,7 +18,6 @@ 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 }) => {
@ -50,35 +49,50 @@ const PagesSection = ({ onItemSelected }: { onItemSelected: () => void }) => {
dispatch(
createNewPageFromEntities(applicationId, name, workspaceId, instanceId),
);
}, [dispatch, pages, applicationId]);
}, [pages, dispatch, applicationId, workspaceId, instanceId]);
const onMenuClose = useCallback(() => setIsMenuOpen(false), [setIsMenuOpen]);
const pageElements = useMemo(
() =>
pages.map((page) => (
<PageElement key={page.pageId} onClick={onItemSelected} page={page} />
<ListItemContainer key={page.pageId}>
<PageElement onClick={onItemSelected} page={page} />
</ListItemContainer>
)),
[pages, location.pathname],
[pages, location.pathname, onItemSelected],
);
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 (
<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>
<ListWithHeader
headerClassName={"pages"}
headerControls={createPageContextMenu}
headerText={`All Pages (${pages.length})`}
maxHeight={"300px"}
>
{pageElements}
</ListWithHeader>
);
};

View File

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

View File

@ -13,6 +13,8 @@ import {
TabPanel,
Button,
Link,
IDEHeader,
IDEHeaderTitle,
} from "@appsmith/ads";
import { useDispatch, useSelector } from "react-redux";
import { EditInteractionKind, SavingState } from "@appsmith/ads-old";
@ -73,11 +75,10 @@ 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%;
@ -89,10 +90,9 @@ const { cloudHosting } = getAppsmithConfigs();
interface HeaderTitleProps {
appState: EditorState;
currentPage?: Page;
}
const HeaderTitleComponent = ({ appState, currentPage }: HeaderTitleProps) => {
const HeaderTitleComponent = ({ appState }: HeaderTitleProps) => {
const libraryHeaderTitle = useLibraryHeaderTitle();
switch (appState) {
@ -104,7 +104,7 @@ const HeaderTitleComponent = ({ appState, currentPage }: HeaderTitleProps) => {
/>
);
case EditorState.EDITOR:
return <EditorTitle key={appState} title={currentPage?.pageName || ""} />;
return <EditorTitle key={appState} />;
case EditorState.SETTINGS:
return (
<IDEHeaderTitle
@ -115,7 +115,7 @@ const HeaderTitleComponent = ({ appState, currentPage }: HeaderTitleProps) => {
case EditorState.LIBRARIES:
return <IDEHeaderTitle key={appState} title={libraryHeaderTitle} />;
default:
return <EditorTitle key={appState} title={currentPage?.pageName || ""} />;
return <EditorTitle key={appState} />;
}
};
@ -218,8 +218,8 @@ const Header = () => {
return (
<>
<IDEHeader>
<IDEHeader.Left>
<HeaderTitleComponent appState={appState} currentPage={currentPage} />
<IDEHeader.Left logo={<AppsmithLink />}>
<HeaderTitleComponent appState={appState} />
<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 "IDE";
import { IDE_HEADER_HEIGHT } from "@appsmith/ads";
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 "IDE";
import { IDE_HEADER_HEIGHT } from "@appsmith/ads";
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 "IDE";
import { IDE_HEADER_HEIGHT } from "@appsmith/ads";
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 "../../../IDE";
import { IDE_HEADER_HEIGHT } from "@appsmith/ads";
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 "IDE";
import { IDE_HEADER_HEIGHT } from "@appsmith/ads";
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 "IDE";
import { IDE_HEADER_HEIGHT } from "@appsmith/ads";
import GeneratePageModal from "./GeneratePage";
interface EditorProps {