Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

chore: Move IDE header to ADS/Templates #37406

Merged
merged 16 commits into from
Nov 26, 2024
Merged
Original file line number Diff line number Diff line change
@@ -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>
);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export { ListItemContainer, ListHeaderContainer } from "./styles";
export { ListWithHeader } from "./ListWithHeader";
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import styled from "styled-components";

export const ListItemContainer = styled.div`
width: 100%;

& .t--entity-item {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You're probably just moving, but applying styling through hardcoded selectors to children is an anti-pattern. The way I see it - such styling should not exist in ADS.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, this is just a move. I have created this into EntityExplorer because this needs to be updated soon. These classes will soon be part of the EntityExplorer template itself.

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;
ankitakinger marked this conversation as resolved.
Show resolved Hide resolved

span {
line-height: 20px;
}
`;
Original file line number Diff line number Diff line change
@@ -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;
`;
Original file line number Diff line number Diff line change
@@ -1,77 +1,78 @@
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,
);

hetunandu marked this conversation as resolved.
Show resolved Hide resolved
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();
});
hetunandu marked this conversation as resolved.
Show resolved Hide resolved

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}
title={undefined}
/>,
);

// 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)",
Expand All @@ -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(" ") || [];

Expand Down
Original file line number Diff line number Diff line change
@@ -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";
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { IDEHeaderSwitcher } from "./IDEHeaderSwitcher";
ankitakinger marked this conversation as resolved.
Show resolved Hide resolved
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export const IDE_HEADER_HEIGHT = 40;
export const LOGO_WIDTH = 50;
Original file line number Diff line number Diff line change
@@ -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} />
Loading
Loading