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:
parent
7ac4df21dd
commit
1ea45e8e48
|
|
@ -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>
|
||||
);
|
||||
};
|
||||
|
|
@ -1,2 +0,0 @@
|
|||
export { ListItemContainer, ListHeaderContainer } from "./styles";
|
||||
export { ListWithHeader } from "./ListWithHeader";
|
||||
|
|
@ -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;
|
||||
}
|
||||
`;
|
||||
|
|
@ -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;
|
||||
`;
|
||||
|
|
@ -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";
|
||||
|
|
@ -1 +0,0 @@
|
|||
export { IDEHeaderSwitcher } from "./IDEHeaderSwitcher";
|
||||
|
|
@ -1,2 +0,0 @@
|
|||
export const IDE_HEADER_HEIGHT = 40;
|
||||
export const LOGO_WIDTH = 50;
|
||||
|
|
@ -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} />
|
||||
|
|
@ -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>
|
||||
);
|
||||
};
|
||||
|
|
@ -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;
|
||||
|
|
@ -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";
|
||||
|
|
@ -1,2 +0,0 @@
|
|||
export * from "./IDEHeader";
|
||||
export * from "./EntityExplorer";
|
||||
|
|
@ -36,4 +36,3 @@ export * from "./Toast";
|
|||
export * from "./ToggleButton";
|
||||
export * from "./Tooltip";
|
||||
export * from "./ToggleButtonGroup";
|
||||
export * from "./Templates";
|
||||
|
|
|
|||
40
app/client/src/IDE/Components/HeaderDropdown.test.tsx
Normal file
40
app/client/src/IDE/Components/HeaderDropdown.test.tsx
Normal 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();
|
||||
});
|
||||
});
|
||||
79
app/client/src/IDE/Components/HeaderDropdown.tsx
Normal file
79
app/client/src/IDE/Components/HeaderDropdown.tsx
Normal 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;
|
||||
|
|
@ -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(" ") || [];
|
||||
|
||||
73
app/client/src/IDE/Components/HeaderEditorSwitcher.tsx
Normal file
73
app/client/src/IDE/Components/HeaderEditorSwitcher.tsx
Normal 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;
|
||||
|
|
@ -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;
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
};
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
);
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
);
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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} {
|
||||
|
|
|
|||
|
|
@ -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`
|
||||
|
|
|
|||
|
|
@ -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%;
|
||||
|
|
|
|||
|
|
@ -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";
|
||||
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user