chore: Visual changes for core navigation elements on IDE (#37880)

## Description

Updates a few visual elements for better Navigational experience using
the Segmented Controls

Fixes #37881

## Automation

/ok-to-test tags="@tag.IDE, @tag.Sanity"

### 🔍 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/12409098657>
> Commit: a94e072ab76b5a1146f25dd822576c6b01e57c1e
> <a
href="https://internal.appsmith.com/app/cypress-dashboard/rundetails-65890b3c81d7400d08fa9ee5?branch=master&workflowId=12409098657&attempt=3"
target="_blank">Cypress dashboard</a>.
> Tags: `@tag.IDE, @tag.Sanity`
> Spec:
> <hr>Thu, 19 Dec 2024 12:51:29 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**
- Added `testId` property to sidebar buttons for enhanced testing
capabilities.
	- Updated button titles from "Data" to "Datasources" for clarity.
- Introduced `BottomButtons` configuration for sidebar buttons based on
data source availability.

- **Bug Fixes**
- Improved visual distinction of selected segments in the Segmented
Control.

- **Style**
- Enhanced styling for sidebar buttons and segments, including hover
effects and separators.

- **Tests**
- Added tests to verify rendering of sidebar buttons with the correct
test IDs.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
This commit is contained in:
Hetu Nandu 2024-12-19 18:57:44 +05:30 committed by GitHub
parent 4a91204e4d
commit 1ade47d31a
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
10 changed files with 77 additions and 41 deletions

View File

@ -6,11 +6,12 @@ import { LeftPane } from "./IDE/LeftPane";
import PageList from "./PageList"; import PageList from "./PageList";
export enum AppSidebarButton { export enum AppSidebarButton {
Data = "Data", Data = "Datasources",
Editor = "Editor", Editor = "Editor",
Libraries = "Libraries", Libraries = "Libraries",
Settings = "Settings", Settings = "Settings",
} }
export const AppSidebar = new Sidebar(Object.values(AppSidebarButton)); export const AppSidebar = new Sidebar(Object.values(AppSidebarButton));
export enum PagePaneSegment { export enum PagePaneSegment {
@ -42,6 +43,7 @@ export enum EntityType {
JSObject = "JSObject", JSObject = "JSObject",
Page = "Page", Page = "Page",
} }
class EditorNavigation { class EditorNavigation {
public locators = { public locators = {
MaximizeBtn: "[data-testid='t--ide-maximize']", MaximizeBtn: "[data-testid='t--ide-maximize']",

View File

@ -2,7 +2,7 @@ export class Sidebar {
buttons: string[]; buttons: string[];
locators = { locators = {
sidebar: ".t--sidebar", sidebar: ".t--sidebar",
sidebarButton: (name: string) => `.t--sidebar-${name}`, sidebarButton: (name: string) => `[data-testid='t--sidebar-${name}']`,
}; };
constructor(buttons: string[]) { constructor(buttons: string[]) {

View File

@ -41,6 +41,12 @@ export const StyledSegment = styled.span`
& > * { & > * {
color: var(--ads-v2-colors-control-segment-value-default-fg); color: var(--ads-v2-colors-control-segment-value-default-fg);
} }
&[data-selected="true"] {
span {
font-weight: var(--ads-v2-font-weight-bold);
}
}
`; `;
export const StyledControlContainer = styled.div` export const StyledControlContainer = styled.div`
@ -81,6 +87,7 @@ export const StyledControlContainer = styled.div`
/* Select all segments which is not a selected and last child */ /* Select all segments which is not a selected and last child */
/* seperator */ /* seperator */
&:not(:hover):not(:last-child):not([data-selected="true"]):not( &:not(:hover):not(:last-child):not([data-selected="true"]):not(
:has(+ [data-selected="true"]) :has(+ [data-selected="true"])
):after { ):after {

View File

@ -1,3 +1,6 @@
<svg width="13" height="12" viewBox="0 0 13 12" fill="none" xmlns="http://www.w3.org/2000/svg"> <svg width="15" height="16" viewBox="0 0 15 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M7.16667 12V5.33333H12.5V12H7.16667ZM0.5 6.66667V0H5.83333V6.66667H0.5ZM4.5 5.33333V1.33333H1.83333V5.33333H4.5ZM0.5 12V8H5.83333V12H0.5ZM1.83333 10.6667H4.5V9.33333H1.83333V10.6667ZM8.5 10.6667H11.1667V6.66667H8.5V10.6667ZM7.16667 0H12.5V4H7.16667V0ZM8.5 1.33333V2.66667H11.1667V1.33333H8.5Z" fill="#4C5664"/> <path
d="M7.67353 13.8542V7.25693H12.3958V13.8542H7.67353ZM1.77075 8.57638V1.97916H6.49297V8.57638H1.77075ZM5.31242 7.25693V3.2986H2.95131V7.25693H5.31242ZM1.77075 13.8542V9.89582H6.49297V13.8542H1.77075ZM2.95131 12.5347H5.31242V11.2153H2.95131V12.5347ZM8.85409 12.5347H11.2152V8.57638H8.85409V12.5347ZM7.67353 1.97916H12.3958V5.93749H7.67353V1.97916ZM8.85409 3.2986V4.61805H11.2152V3.2986H8.85409Z"
fill="currentColor"/>
</svg> </svg>

Before

Width:  |  Height:  |  Size: 423 B

After

Width:  |  Height:  |  Size: 536 B

View File

@ -44,6 +44,7 @@ function IDESidebar(props: IDESidebarProps) {
key={button.state} key={button.state}
onClick={onClick} onClick={onClick}
selected={editorState === button.state} selected={editorState === button.state}
testId={button.testId}
title={button.title} title={button.title}
tooltip={button.tooltip} tooltip={button.tooltip}
urlSuffix={button.urlSuffix} urlSuffix={button.urlSuffix}
@ -58,6 +59,7 @@ function IDESidebar(props: IDESidebarProps) {
key={button.state} key={button.state}
onClick={onClick} onClick={onClick}
selected={editorState === button.state} selected={editorState === button.state}
testId={button.testId}
title={button.title} title={button.title}
tooltip={button.tooltip} tooltip={button.tooltip}
urlSuffix={button.urlSuffix} urlSuffix={button.urlSuffix}

View File

@ -11,9 +11,16 @@ const sidebarButtonProps: SidebarButtonProps = {
selected: false, selected: false,
title: "Test", title: "Test",
urlSuffix: "/test", urlSuffix: "/test",
testId: "testId",
}; };
describe("SidebarButton", () => { describe("SidebarButton", () => {
it("should render the button with the correct test id", () => {
const { getByTestId } = render(<SidebarButton {...sidebarButtonProps} />);
expect(getByTestId("t--sidebar-testId")).toBeDefined();
});
it("should render the warning icon in case the datasource list is empty", () => { it("should render the warning icon in case the datasource list is empty", () => {
const withWarningCondition = { const withWarningCondition = {
...sidebarButtonProps, ...sidebarButtonProps,

View File

@ -16,6 +16,7 @@ const ConditionConfig: Record<Condition, { icon: string; color: string }> = {
export interface SidebarButtonProps { export interface SidebarButtonProps {
title?: string; title?: string;
testId: string;
selected: boolean; selected: boolean;
icon: string; icon: string;
onClick: (urlSuffix: string) => void; onClick: (urlSuffix: string) => void;
@ -33,10 +34,8 @@ const Container = styled(Flex)`
padding: 8px 0; padding: 8px 0;
`; `;
const IconContainer = styled.div<{ selected: boolean }>` const IconContainer = styled.div`
padding: 2px; padding: 2px;
background-color: ${(props) =>
props.selected ? "var(--colors-raw-orange-100, #fbe6dc)" : "white"};
border-radius: 3px; border-radius: 3px;
width: 32px; width: 32px;
height: 32px; height: 32px;
@ -46,11 +45,16 @@ const IconContainer = styled.div<{ selected: boolean }>`
cursor: pointer; cursor: pointer;
position: relative; position: relative;
&[data-selected="false"] {
background-color: var(--ads-v2-color-bg);
&:hover { &:hover {
background: ${(props) => background-color: var(--ads-v2-color-bg-subtle, #f1f5f9);
props.selected }
? "var(--colors-raw-orange-100, #fbe6dc)" }
: "var(--ads-v2-color-bg-subtle, #f1f5f9);"};
&[data-selected="true"] {
background-color: var(--ads-v2-color-bg-muted);
} }
`; `;
@ -85,9 +89,9 @@ function SidebarButton(props: SidebarButtonProps) {
<IconContainer <IconContainer
className={`t--sidebar-${title || tooltip}`} className={`t--sidebar-${title || tooltip}`}
data-selected={selected} data-selected={selected}
data-testid={"t--sidebar-" + props.testId}
onClick={handleOnClick} onClick={handleOnClick}
role="button" role="button"
selected={selected}
> >
<Icon name={icon} size="lg" /> <Icon name={icon} size="lg" />
{condition && ( {condition && (

View File

@ -31,11 +31,11 @@ export enum EditorState {
} }
export const SidebarTopButtonTitles = { export const SidebarTopButtonTitles = {
DATA: "Data",
EDITOR: "Editor", EDITOR: "Editor",
}; };
export const SidebarBottomButtonTitles = { export const SidebarBottomButtonTitles = {
DATA: "Datasources",
SETTINGS: "Settings", SETTINGS: "Settings",
LIBRARIES: "Libraries", LIBRARIES: "Libraries",
}; };
@ -62,27 +62,31 @@ export const TopButtons: IDESidebarButton[] = [
state: EditorState.EDITOR, state: EditorState.EDITOR,
icon: "editor-v3", icon: "editor-v3",
title: SidebarTopButtonTitles.EDITOR, title: SidebarTopButtonTitles.EDITOR,
testId: SidebarTopButtonTitles.EDITOR,
urlSuffix: "", urlSuffix: "",
}, },
{
state: EditorState.DATA,
icon: "datasource-v3",
title: SidebarTopButtonTitles.DATA,
urlSuffix: "datasource",
},
]; ];
export const BottomButtons: IDESidebarButton[] = [ export const BottomButtons: IDESidebarButton[] = [
{
state: EditorState.DATA,
icon: "datasource-v3",
tooltip: SidebarBottomButtonTitles.DATA,
testId: SidebarBottomButtonTitles.DATA,
urlSuffix: "datasource",
},
{ {
state: EditorState.LIBRARIES, state: EditorState.LIBRARIES,
icon: "packages-v3", icon: "packages-v3",
tooltip: SidebarBottomButtonTitles.LIBRARIES, tooltip: SidebarBottomButtonTitles.LIBRARIES,
testId: SidebarBottomButtonTitles.LIBRARIES,
urlSuffix: "libraries", urlSuffix: "libraries",
}, },
{ {
state: EditorState.SETTINGS, state: EditorState.SETTINGS,
icon: "settings-v3", icon: "settings-v3",
tooltip: SidebarBottomButtonTitles.SETTINGS, tooltip: SidebarBottomButtonTitles.SETTINGS,
testId: SidebarBottomButtonTitles.SETTINGS,
urlSuffix: "settings", urlSuffix: "settings",
}, },
]; ];

View File

@ -1,4 +1,4 @@
import React from "react"; import React, { useMemo } from "react";
import { createMessage, EDITOR_PANE_TEXTS } from "ee/constants/messages"; import { createMessage, EDITOR_PANE_TEXTS } from "ee/constants/messages";
import { EditorEntityTab } from "ee/entities/IDE/constants"; import { EditorEntityTab } from "ee/entities/IDE/constants";
import { useCurrentEditorState, useSegmentNavigation } from "../../hooks"; import { useCurrentEditorState, useSegmentNavigation } from "../../hooks";
@ -8,23 +8,30 @@ const SegmentSwitcher = () => {
const { segment } = useCurrentEditorState(); const { segment } = useCurrentEditorState();
const { onSegmentChange } = useSegmentNavigation(); const { onSegmentChange } = useSegmentNavigation();
return ( const segmentOptions = useMemo(() => {
<EditorSegments return [
onSegmentChange={onSegmentChange}
options={[
{ {
label: createMessage(EDITOR_PANE_TEXTS.queries_tab), label: createMessage(EDITOR_PANE_TEXTS.queries_tab),
startIcon: "queries-line",
value: EditorEntityTab.QUERIES, value: EditorEntityTab.QUERIES,
}, },
{ {
label: createMessage(EDITOR_PANE_TEXTS.js_tab), label: createMessage(EDITOR_PANE_TEXTS.js_tab),
startIcon: "content-type-json",
value: EditorEntityTab.JS, value: EditorEntityTab.JS,
}, },
{ {
label: createMessage(EDITOR_PANE_TEXTS.ui_tab), label: createMessage(EDITOR_PANE_TEXTS.ui_tab),
startIcon: "dashboard-line",
value: EditorEntityTab.UI, value: EditorEntityTab.UI,
}, },
]} ];
}, []);
return (
<EditorSegments
onSegmentChange={onSegmentChange}
options={segmentOptions}
selectedSegment={segment} selectedSegment={segment}
/> />
); );

View File

@ -26,11 +26,11 @@ function Sidebar() {
const datasources = useSelector(getDatasources); const datasources = useSelector(getDatasources);
const datasourcesExist = datasources.length > 0; const datasourcesExist = datasources.length > 0;
// Updates the top button config based on datasource existence // Updates the bottom button config based on datasource existence
const topButtons = React.useMemo(() => { const bottomButtons = React.useMemo(() => {
return datasourcesExist return datasourcesExist
? TopButtons ? BottomButtons
: TopButtons.map((button) => { : BottomButtons.map((button) => {
if (button.state === EditorState.DATA) { if (button.state === EditorState.DATA) {
return { return {
...button, ...button,
@ -64,11 +64,11 @@ function Sidebar() {
return ( return (
<IDESidebar <IDESidebar
bottomButtons={BottomButtons} bottomButtons={bottomButtons}
editorState={appState} editorState={appState}
id={"t--app-sidebar"} id={"t--app-sidebar"}
onClick={onClick} onClick={onClick}
topButtons={topButtons} topButtons={TopButtons}
/> />
); );
} }