diff --git a/app/client/packages/design-system/ads/src/Templates/EntityExplorer/ListWithHeader.tsx b/app/client/packages/design-system/ads/src/Templates/EntityExplorer/ListWithHeader.tsx new file mode 100644 index 00000000000..2f7f3041c58 --- /dev/null +++ b/app/client/packages/design-system/ads/src/Templates/EntityExplorer/ListWithHeader.tsx @@ -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 ( + + + {props.headerText} + {props.headerControls} + + + {props.children} + + + ); +}; diff --git a/app/client/packages/design-system/ads/src/Templates/EntityExplorer/index.ts b/app/client/packages/design-system/ads/src/Templates/EntityExplorer/index.ts new file mode 100644 index 00000000000..cd175bbdf5e --- /dev/null +++ b/app/client/packages/design-system/ads/src/Templates/EntityExplorer/index.ts @@ -0,0 +1,2 @@ +export { ListItemContainer, ListHeaderContainer } from "./styles"; +export { ListWithHeader } from "./ListWithHeader"; diff --git a/app/client/packages/design-system/ads/src/Templates/EntityExplorer/styles.ts b/app/client/packages/design-system/ads/src/Templates/EntityExplorer/styles.ts new file mode 100644 index 00000000000..df4b639c91e --- /dev/null +++ b/app/client/packages/design-system/ads/src/Templates/EntityExplorer/styles.ts @@ -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; + } +`; diff --git a/app/client/packages/design-system/ads/src/Templates/IDEHeader/HeaderSwitcher/HeaderSwitcher.styles.ts b/app/client/packages/design-system/ads/src/Templates/IDEHeader/HeaderSwitcher/HeaderSwitcher.styles.ts new file mode 100644 index 00000000000..06309d2d20c --- /dev/null +++ b/app/client/packages/design-system/ads/src/Templates/IDEHeader/HeaderSwitcher/HeaderSwitcher.styles.ts @@ -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; +`; diff --git a/app/client/src/IDE/Components/HeaderEditorSwitcher.test.tsx b/app/client/packages/design-system/ads/src/Templates/IDEHeader/HeaderSwitcher/HeaderSwitcher.test.tsx similarity index 59% rename from app/client/src/IDE/Components/HeaderEditorSwitcher.test.tsx rename to app/client/packages/design-system/ads/src/Templates/IDEHeader/HeaderSwitcher/HeaderSwitcher.test.tsx index e9bb4973882..d248d3f518b 100644 --- a/app/client/src/IDE/Components/HeaderEditorSwitcher.test.tsx +++ b/app/client/packages/design-system/ads/src/Templates/IDEHeader/HeaderSwitcher/HeaderSwitcher.test.tsx @@ -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: Test, }; it("renders with correct props", () => { - const { getByText } = render(); + render(); + // 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( - , - ); + render(); - 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(); + render(); - fireEvent.click(getByText("Title")); + fireEvent.click(screen.getByText("Title")); expect(mockOnClick).toHaveBeenCalled(); }); it("forwards ref correctly", () => { - const ref = React.createRef(); + const ref = React.createRef(); - render(); + render(); expect(ref.current).toBeTruthy(); }); it("does not crash when onClick is not provided", () => { - const { getByText } = render( - , - ); + render(); - 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( - (); + const { container } = render( + { />, ); + // 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( - , + > + Test + , ); + // 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(" ") || []; diff --git a/app/client/packages/design-system/ads/src/Templates/IDEHeader/HeaderSwitcher/IDEHeaderSwitcher.tsx b/app/client/packages/design-system/ads/src/Templates/IDEHeader/HeaderSwitcher/IDEHeaderSwitcher.tsx new file mode 100644 index 00000000000..22928a5f499 --- /dev/null +++ b/app/client/packages/design-system/ads/src/Templates/IDEHeader/HeaderSwitcher/IDEHeaderSwitcher.tsx @@ -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; + className?: string; + children: React.ReactNode; +} + +export const IDEHeaderSwitcher = React.forwardRef( + (props: Props, ref: ForwardedRef) => { + const { + active, + children, + className, + onClick, + prefix, + setActive, + title, + titleTestId, + ...rest + } = props; + + const separator = title ? " /" : ""; + + const closeSwitcher = useCallback(() => { + return setActive(false); + }, [setActive]); + + return ( + + + + + {prefix + separator} + + + + {title} + + + + + + + {children} + + + ); + }, +); + +IDEHeaderSwitcher.displayName = "IDEHeaderSwitcher"; diff --git a/app/client/packages/design-system/ads/src/Templates/IDEHeader/HeaderSwitcher/index.ts b/app/client/packages/design-system/ads/src/Templates/IDEHeader/HeaderSwitcher/index.ts new file mode 100644 index 00000000000..1580b3834c6 --- /dev/null +++ b/app/client/packages/design-system/ads/src/Templates/IDEHeader/HeaderSwitcher/index.ts @@ -0,0 +1 @@ +export { IDEHeaderSwitcher } from "./IDEHeaderSwitcher"; diff --git a/app/client/packages/design-system/ads/src/Templates/IDEHeader/IDEHeader.constants.ts b/app/client/packages/design-system/ads/src/Templates/IDEHeader/IDEHeader.constants.ts new file mode 100644 index 00000000000..6307b9c5b69 --- /dev/null +++ b/app/client/packages/design-system/ads/src/Templates/IDEHeader/IDEHeader.constants.ts @@ -0,0 +1,2 @@ +export const IDE_HEADER_HEIGHT = 40; +export const LOGO_WIDTH = 50; diff --git a/app/client/packages/design-system/ads/src/Templates/IDEHeader/IDEHeader.mdx b/app/client/packages/design-system/ads/src/Templates/IDEHeader/IDEHeader.mdx new file mode 100644 index 00000000000..37c3900615a --- /dev/null +++ b/app/client/packages/design-system/ads/src/Templates/IDEHeader/IDEHeader.mdx @@ -0,0 +1,28 @@ +import { Canvas, Meta } from "@storybook/blocks"; +import * as IDEHeaderStories from "./IDEHeader.stories"; + + + +# 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. + + + +## 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. + + + +#### Header Dropdown + +A dropdown that allows the user to switch between different pages. + + diff --git a/app/client/packages/design-system/ads/src/Templates/IDEHeader/IDEHeader.stories.tsx b/app/client/packages/design-system/ads/src/Templates/IDEHeader/IDEHeader.stories.tsx new file mode 100644 index 00000000000..6c709080dd5 --- /dev/null +++ b/app/client/packages/design-system/ads/src/Templates/IDEHeader/IDEHeader.stories.tsx @@ -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) => ( +
{Story()}
+ ), + ], +}; + +export default meta; + +export const Default = () => ( + + }> + Left Content + + + Center Content + + + Right Content + + +); + +export const WithHeaderTitle = () => { + return ( + + }> + + + +
+ + +
+ + + ); +}; + +export const WithHeaderDropdown = () => { + const [open, setOpen] = React.useState(false); + + return ( + + }> + + + + Pages +