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 Sidebar to IDE/Components #34487

Merged
merged 16 commits into from
Jul 10, 2024
Merged
4 changes: 2 additions & 2 deletions app/client/src/IDE/Components/BottomView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@ import styled from "styled-components";
import Resizer, {
ResizerCSS,
} from "components/editorComponents/Debugger/Resizer";
import { CodeEditorWithGutterStyles } from "pages/Editor/JSEditor/constants";
import { ViewHideBehaviour, ViewDisplayMode } from "IDE/Interfaces/View";
import { CodeEditorWithGutterStyles } from "pages/Editor/JSEditor/styledComponents";
import { ViewDisplayMode, ViewHideBehaviour } from "IDE/Interfaces/View";
import { Button } from "design-system";

const VIEW_MIN_HEIGHT = 38;
Copy link
Contributor

Choose a reason for hiding this comment

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

Not critical by any means, but it would be more appropriate for constants to be in a separate file.

Expand Down
69 changes: 69 additions & 0 deletions app/client/src/IDE/Components/Sidebar/Sidebar.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
import React from "react";
import styled from "styled-components";
import SidebarButton from "./SidebarButton";
import type { EditorState } from "@appsmith/entities/IDE/constants";
import type { SidebarButtonProps } from "./SidebarButton/SidebarButton";
import { Flex } from "design-system";

const Container = styled(Flex)`
width: 50px;
border-right: 1px solid var(--ads-v2-color-border);
height: 100%;
flex-direction: column;
justify-content: space-between;
background-color: var(--ads-v2-color-bg);
position: relative;
`;

// Sidebar handles the correct handling of sidebar button. It will check if
// the button should be selected and only handle calling the onClick
export interface IDESidebarButton
extends Omit<SidebarButtonProps, "onClick" | "selected"> {
state: EditorState;
urlSuffix: string;
}

interface IDESidebarProps {
id?: string;
topButtons: IDESidebarButton[];
bottomButtons: IDESidebarButton[];
editorState: EditorState;
onClick: (suffix: string) => void;
}

function IDESidebar(props: IDESidebarProps) {
const { bottomButtons, editorState, onClick, topButtons } = props;

return (
<Container className="t--sidebar" id={props.id}>
<div>
{topButtons.map((button) => (
<SidebarButton
icon={button.icon}
key={button.state}
onClick={onClick}
selected={editorState === button.state}
title={button.title}
tooltip={button.tooltip}
urlSuffix={button.urlSuffix}
/>
))}
</div>
<div>
{bottomButtons.map((button) => (
<SidebarButton
icon={button.icon}
key={button.state}
onClick={onClick}
selected={editorState === button.state}
title={button.title}
tooltip={button.tooltip}
urlSuffix={button.urlSuffix}
/>
))}
</div>
</Container>
);
}

export default IDESidebar;
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import { render } from "test/testUtils";
import React from "react";
import SidebarButton, { type SidebarButtonProps } from "./SidebarButton";

import { Condition } from "../../../enums";
import userEvent from "@testing-library/user-event";

const sidebarButtonProps: SidebarButtonProps = {
icon: "down-arrow",
onClick: () => {},
selected: false,
title: "Test",
urlSuffix: "/test",
};

describe("SidebarButton", () => {
it("should render the warning icon in case the datasource list is empty", () => {
const withWarningCondition = {
...sidebarButtonProps,
condition: Condition.Warn,
};

const { container } = render(<SidebarButton {...withWarningCondition} />);

const svgs = container.querySelectorAll("svg");
expect(svgs).toHaveLength(2);
});

it("should call onClick with urlSuffix", async () => {
const checkOnClick = {
...sidebarButtonProps,
onClick: jest.fn(),
};
const { getByRole } = render(<SidebarButton {...checkOnClick} />);

await userEvent.click(getByRole("button"));
expect(checkOnClick.onClick).toHaveBeenCalledWith(checkOnClick.urlSuffix);
});

it("should not call onClick when button is already selected", async () => {
const withSelected = {
...sidebarButtonProps,
selected: true,
onClick: jest.fn(),
};
const { getByRole } = render(<SidebarButton {...withSelected} />);

await userEvent.click(getByRole("button"));
expect(withSelected.onClick).not.toHaveBeenCalled();
});
});
106 changes: 106 additions & 0 deletions app/client/src/IDE/Components/Sidebar/SidebarButton/SidebarButton.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
import React, { useCallback } from "react";
import { Flex, Icon, Text, Tooltip } from "design-system";
import styled from "styled-components";

import { Condition } from "../../../enums";

const ConditionConfig: Record<Condition, { icon: string; color: string }> = {
[Condition.Warn]: {
icon: "warning",
color: "#ffe283",
},
// TODO add this information for further conditions
// Error: { color: "", icon: "" },
// Success: { color: "", icon: "" },
};

export interface SidebarButtonProps {
title?: string;
selected: boolean;
icon: string;
onClick: (urlSuffix: string) => void;
urlSuffix: string;
tooltip?: string;
condition?: Condition;
}

const Container = styled(Flex)`
Copy link
Collaborator

Choose a reason for hiding this comment

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

All these can be passed to the Flex component as prop.

Copy link
Collaborator

Choose a reason for hiding this comment

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

justify-content: center;
flex-direction: column;
width: 50px;
text-align: center;
align-items: center;
padding: 8px 0;
`;

const IconContainer = styled.div<{ selected: boolean }>`
padding: 2px;
background-color: ${(props) =>
props.selected ? "var(--colors-raw-orange-100, #fbe6dc)" : "white"};
border-radius: 3px;
width: 32px;
height: 32px;
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
position: relative;

&:hover {
background: ${(props) =>
props.selected
? "var(--colors-raw-orange-100, #fbe6dc)"
: "var(--ads-v2-color-bg-subtle, #f1f5f9);"};
}
`;

const ConditionIcon = styled(Icon)`
position: absolute;
bottom: 3px;
right: -1px;

&.t--sidebar-${Condition.Warn}-condition-icon {
color: ${ConditionConfig[Condition.Warn].color};
}

// TODO add more condition colors here
`;

function SidebarButton(props: SidebarButtonProps) {
const { condition, icon, onClick, selected, title, tooltip, urlSuffix } =
props;
const handleOnClick = useCallback(() => {
if (!selected) {
onClick(urlSuffix);
}
}, [selected, onClick, urlSuffix]);
return (
<Container>
<Tooltip
content={tooltip}
isDisabled={!!title && !tooltip}
placement={"right"}
>
<IconContainer
className={`t--sidebar-${title || tooltip}`}
data-selected={selected}
onClick={handleOnClick}
role="button"
selected={selected}
>
<Icon name={icon} size="lg" />
{condition && (
<ConditionIcon
className={`t--sidebar-${condition}-condition-icon`}
name={ConditionConfig[condition].icon}
size="md"
/>
)}
</IconContainer>
</Tooltip>
{title ? <Text kind="body-s">{title}</Text> : null}
</Container>
);
}

export default SidebarButton;
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { default } from "./SidebarButton";
2 changes: 2 additions & 0 deletions app/client/src/IDE/Components/Sidebar/index.tsx
alex-golovanov marked this conversation as resolved.
Show resolved Hide resolved
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export { default } from "./Sidebar";
export type { IDESidebarButton } from "./Sidebar";
5 changes: 5 additions & 0 deletions app/client/src/IDE/enums.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
export enum Condition {
Copy link
Contributor

Choose a reason for hiding this comment

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

Because it is being exported for outside consumption, maybe a more descriptive name should be considered. Condition appears a bit too generic.

Warn = "Warn",
// Error = "Error",
// Success = "Success",
}
8 changes: 8 additions & 0 deletions app/client/src/IDE/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,9 +40,17 @@ export { default as IDEHeaderDropdown } from "./Components/HeaderDropdown";
*/
export { default as IDEBottomView } from "./Components/BottomView";

/**
* IDESidebar is used inside the IDE to have a navigation menu on the left side of the screen.
* It switches between different editor states
*/
export { default as IDESidebar } from "./Components/Sidebar";

/* ====================================================
**** Interfaces ****
Common types that are used by the different components of the IDE
=======================================================**/

export { ViewHideBehaviour, ViewDisplayMode } from "./Interfaces/View";
export { Condition } from "./enums";
export type { IDESidebarButton } from "./Components/Sidebar";
2 changes: 1 addition & 1 deletion app/client/src/api/LibraryAPI.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ export default class LibraryApi extends Api {
library: Partial<JSLibrary>,
) {
const url = LibraryApi.getUpdateLibraryBaseURL(applicationId) + "/remove";
return Api.patch(url, { accessor: library.accessor, url: library.url });
return Api.patch(url, library);
hetunandu marked this conversation as resolved.
Show resolved Hide resolved
}

static async getLibraries(applicationId: string, mode: APP_MODE) {
Expand Down
30 changes: 6 additions & 24 deletions app/client/src/ce/entities/IDE/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,11 +21,8 @@ import {
SAAS_EDITOR_DATASOURCE_ID_PATH,
} from "pages/Editor/SaaSEditor/constants";
import type { PluginType } from "entities/Action";
import type { ReactNode, ComponentType } from "react";
import {
EMPTY_DATASOURCE_TOOLTIP_SIDEBUTTON,
createMessage,
} from "@appsmith/constants/messages";
import type { ComponentType, ReactNode } from "react";
import type { IDESidebarButton } from "IDE";

export enum EditorState {
DATA = "DATA",
Expand Down Expand Up @@ -61,20 +58,7 @@ export enum EditorViewMode {
SplitScreen = "SplitScreen",
}

export enum SideButtonType {
DATSOURCE = "DATASOURCE",
}

export interface SidebarButton {
state: EditorState;
icon: string;
title?: string;
urlSuffix: string;
conditionType?: SideButtonType;
conditionTooltip?: string;
}

export const TopButtons: SidebarButton[] = [
export const TopButtons: IDESidebarButton[] = [
{
state: EditorState.EDITOR,
icon: "editor-v3",
Expand All @@ -86,22 +70,20 @@ export const TopButtons: SidebarButton[] = [
icon: "datasource-v3",
title: SidebarTopButtonTitles.DATA,
urlSuffix: "datasource",
conditionType: SideButtonType.DATSOURCE,
conditionTooltip: createMessage(EMPTY_DATASOURCE_TOOLTIP_SIDEBUTTON),
},
];

export const BottomButtons: SidebarButton[] = [
export const BottomButtons: IDESidebarButton[] = [
{
state: EditorState.LIBRARIES,
icon: "packages-v3",
title: SidebarBottomButtonTitles.LIBRARIES,
tooltip: SidebarBottomButtonTitles.LIBRARIES,
urlSuffix: "libraries",
},
{
state: EditorState.SETTINGS,
icon: "settings-v3",
title: SidebarBottomButtonTitles.SETTINGS,
tooltip: SidebarBottomButtonTitles.SETTINGS,
urlSuffix: "settings",
},
];
Expand Down
Loading
Loading