chore: Moving navigation header to Explorer templates in ADS (#38131)

## Description

Moving navigation header to Explorer templates in ADS

Fixes [#37614](https://github.com/appsmithorg/appsmith/issues/37614)
[#37612](https://github.com/appsmithorg/appsmith/issues/37612)
[#37611](https://github.com/appsmithorg/appsmith/issues/37611)

## 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/12380719410>
> Commit: 440da6dff557348bd4524127c86ecaeee19dfe41
> <a
href="https://internal.appsmith.com/app/cypress-dashboard/rundetails-65890b3c81d7400d08fa9ee5?branch=master&workflowId=12380719410&attempt=1"
target="_blank">Cypress dashboard</a>.
> Tags: `@tag.IDE, @tag.Sanity`
> Spec:
> <hr>Tue, 17 Dec 2024 21:00:53 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

- **New Features**
- Introduced a new `SegmentSwitcher` component for enhanced segment
navigation.
- Added `EditorSegments` component for rendering a segmented control
interface.
- **Improvements**
- Replaced the `SegmentedHeader` with `SegmentSwitcher` in the explorer
pane for improved user experience.
- Updated the header component to use the new `NavigationHeader` for
better segment management.
- Enhanced hover interactions and simplified separator logic within the
segmented control.
- **Exports**
- New exports for `EditorSegments` and `SegmentSwitcher` components to
facilitate usage across modules.
- Added new documentation for the `EditorSegments` component to provide
usage guidelines and examples.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
This commit is contained in:
Ankita Kinger 2024-12-18 11:26:02 +05:30 committed by GitHub
parent 35a5d83ec7
commit 495f139a83
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
12 changed files with 226 additions and 68 deletions

View File

@ -55,6 +55,10 @@ export const StyledControlContainer = styled.div`
cursor: pointer;
height: 100%;
&[data-selected="false"]:hover {
background-color: var(--ads-v2-color-bg-muted);
}
&:focus-visible {
outline: var(--ads-v2-border-width-outline) solid
var(--ads-v2-color-outline);
@ -77,7 +81,7 @@ export const StyledControlContainer = styled.div`
/* Select all segments which is not a selected and last child */
/* seperator */
&:not(:last-child):not([data-selected="true"]):not(
&:not(:hover):not(:last-child):not([data-selected="true"]):not(
:has(+ [data-selected="true"])
):after {
content: "";
@ -87,15 +91,4 @@ export const StyledControlContainer = styled.div`
height: 16px;
background-color: var(--ads-v2-colors-control-field-default-border);
}
/* This before is to mask the separator in left side of selected control */
/* Mask the seperator with track background color */
&[data-selected="true"]:not(:first-child):after {
content: "";
position: absolute;
left: -7px;
width: 2px;
height: 16px;
background-color: var(--ads-v2-colors-control-track-default-bg);
}
`;

View File

@ -0,0 +1,25 @@
import { Canvas, Meta } from "@storybook/blocks";
import * as EditorSegmentsStories from "./EditorSegments.stories";
<Meta of={EditorSegmentsStories} />
# Editor Segments
Editor Segments is is built on top of the ADS component - Segmented Control. It is a preset template built for Entity Explorer which has a `max-width of 247px` and has extra padding around each of the segments.
Editor Segments present a range of options, and should be used when the user can execute those options instantaneously. It can also accept `children` to add more UI controls, if needed. These will be placed on the right side of the segments. It takes `options`, `selectedSegment` and `onSegmentChange` props to handle the value and onChange functionalities of Segmented Control component.
The options presented should not be binary. If you have a `yes | no` scenario, use a switch, instead.
## Anatomy
### Default implementation
Note: The `options` needs to be passed as required, the UI shown below are not default values being provided to the Editor segments. It also means the `selectedSegment` and `onSegmentChange` props needs to be passed as well.
<Canvas of={EditorSegmentsStories.EditorSegmentsStory} />
### More UI controls
<Canvas of={EditorSegmentsStories.EditorSegmentsStoryWithChildren} />

View File

@ -0,0 +1,101 @@
import React from "react";
import { EditorSegments } from "./EditorSegments";
import type { EditorSegmentsProps } from "./EditorSegments.types";
import type { StoryObj } from "@storybook/react";
import { Button } from "../../../Button";
export default {
title: "ADS/Templates/Entity Explorer/Editor Segments",
component: EditorSegments,
};
// eslint-disable-next-line react/function-component-definition
const Template = (args: EditorSegmentsProps) => {
return <EditorSegments {...args}>{args.children}</EditorSegments>;
};
export const EditorSegmentsStory = Template.bind({}) as StoryObj;
EditorSegmentsStory.storyName = "Default";
EditorSegmentsStory.args = {
options: [
{
value: "queries",
label: "Queries",
startIcon: "queries-v3",
},
{
value: "js",
label: "JS",
startIcon: "content-type-json",
},
{
value: "ui",
label: "UI",
startIcon: "dashboard-line",
isDisabled: true,
},
],
defaultValue: "queries",
};
export const EditorSegmentsStoryWithIcons = Template.bind({}) as StoryObj;
EditorSegmentsStoryWithIcons.storyName = "Only Icons";
EditorSegmentsStoryWithIcons.args = {
options: [
{
value: "queries",
startIcon: "queries-v3",
},
{
value: "js",
startIcon: "content-type-json",
},
{
value: "ui",
startIcon: "dashboard-line",
},
],
defaultValue: "queries",
};
export const EditorSegmentsStoryWithLabels = Template.bind({}) as StoryObj;
EditorSegmentsStoryWithLabels.storyName = "Only Labels";
EditorSegmentsStoryWithLabels.args = {
options: [
{
value: "queries",
label: "Queries",
},
{
value: "js",
label: "JS",
},
{
value: "ui",
label: "UI",
},
],
defaultValue: "queries",
};
export const EditorSegmentsStoryWithChildren = Template.bind({}) as StoryObj;
EditorSegmentsStoryWithChildren.storyName = "With Children";
EditorSegmentsStoryWithChildren.args = {
options: [
{
value: "queries",
label: "Queries",
},
{
value: "js",
label: "JS",
},
{
value: "ui",
label: "UI",
},
],
defaultValue: "queries",
isFullWidth: true,
children: <Button isIconButton kind="secondary" startIcon="plus" />,
};

View File

@ -0,0 +1,8 @@
import styled from "styled-components";
import { Flex } from "../../../Flex";
export const Container = styled(Flex)`
.editor-pane-segment-control {
max-width: 247px;
}
`;

View File

@ -0,0 +1,33 @@
import React from "react";
import { SegmentedControl } from "../../../SegmentedControl";
import { Container } from "./EditorSegments.styles";
import type { EditorSegmentsProps } from "./EditorSegments.types";
const EditorSegments = (props: EditorSegmentsProps) => {
const { children, onSegmentChange, options, selectedSegment } = props;
return (
<Container
alignItems="center"
backgroundColor="var(--ads-v2-colors-control-track-default-bg)"
className="ide-editor-left-pane__header"
gap="spaces-2"
justifyContent="space-between"
padding="spaces-2"
>
<SegmentedControl
className="editor-pane-segment-control"
onChange={onSegmentChange}
options={options}
value={selectedSegment}
/>
{children}
</Container>
);
};
EditorSegments.displayName = "EditorSegments";
EditorSegments.defaultProps = {};
export { EditorSegments };

View File

@ -0,0 +1,8 @@
import type { SegmentedControlOption } from "../../../SegmentedControl";
export interface EditorSegmentsProps {
selectedSegment: string;
onSegmentChange: (value: string) => void;
options: SegmentedControlOption[];
children?: React.ReactNode | React.ReactNode[];
}

View File

@ -0,0 +1,2 @@
export * from "./EditorSegments";
export * from "./EditorSegments.types";

View File

@ -1,2 +1,3 @@
export { ListItemContainer, ListHeaderContainer } from "./styles";
export { ListWithHeader } from "./ListWithHeader";
export { EditorSegments } from "./EditorSegments";

View File

@ -15,7 +15,7 @@ import {
BUILDER_PATH,
BUILDER_PATH_DEPRECATED,
} from "ee/constants/routes/appRoutes";
import SegmentedHeader from "./components/SegmentedHeader";
import SegmentSwitcher from "./components/SegmentSwitcher";
import { useSelector } from "react-redux";
import { getIDEViewMode } from "selectors/ideSelectors";
import { EditorViewMode } from "ee/entities/IDE/constants";
@ -41,7 +41,7 @@ const EditorPaneExplorer = () => {
: "100%"
}
>
<SegmentedHeader />
<SegmentSwitcher />
<Switch>
<SentryRoute
component={JSExplorer}

View File

@ -0,0 +1,33 @@
import React from "react";
import { createMessage, EDITOR_PANE_TEXTS } from "ee/constants/messages";
import { EditorEntityTab } from "ee/entities/IDE/constants";
import { useCurrentEditorState, useSegmentNavigation } from "../../hooks";
import { EditorSegments } from "@appsmith/ads";
const SegmentSwitcher = () => {
const { segment } = useCurrentEditorState();
const { onSegmentChange } = useSegmentNavigation();
return (
<EditorSegments
onSegmentChange={onSegmentChange}
options={[
{
label: createMessage(EDITOR_PANE_TEXTS.queries_tab),
value: EditorEntityTab.QUERIES,
},
{
label: createMessage(EDITOR_PANE_TEXTS.js_tab),
value: EditorEntityTab.JS,
},
{
label: createMessage(EDITOR_PANE_TEXTS.ui_tab),
value: EditorEntityTab.UI,
},
]}
selectedSegment={segment}
/>
);
};
export default SegmentSwitcher;

View File

@ -1,50 +0,0 @@
import React from "react";
import { Flex, SegmentedControl } from "@appsmith/ads";
import { createMessage, EDITOR_PANE_TEXTS } from "ee/constants/messages";
import { EditorEntityTab } from "ee/entities/IDE/constants";
import { useCurrentEditorState, useSegmentNavigation } from "../../hooks";
import styled from "styled-components";
const Container = styled(Flex)`
#editor-pane-segment-control {
max-width: 247px;
}
`;
const SegmentedHeader = () => {
const { segment } = useCurrentEditorState();
const { onSegmentChange } = useSegmentNavigation();
return (
<Container
alignItems="center"
backgroundColor="var(--ads-v2-colors-control-track-default-bg)"
className="ide-editor-left-pane__header"
gap="spaces-2"
justifyContent="space-between"
padding="spaces-2"
>
<SegmentedControl
id="editor-pane-segment-control"
onChange={onSegmentChange}
options={[
{
label: createMessage(EDITOR_PANE_TEXTS.queries_tab),
value: EditorEntityTab.QUERIES,
},
{
label: createMessage(EDITOR_PANE_TEXTS.js_tab),
value: EditorEntityTab.JS,
},
{
label: createMessage(EDITOR_PANE_TEXTS.ui_tab),
value: EditorEntityTab.UI,
},
]}
value={segment}
/>
</Container>
);
};
export default SegmentedHeader;

View File

@ -27,6 +27,8 @@ import { closeJSActionTab } from "actions/jsActionActions";
import { closeQueryActionTab } from "actions/pluginActionActions";
import { getCurrentBasePageId } from "selectors/editorSelectors";
import { getCurrentEntityInfo } from "../utils";
import { useEditorType } from "ee/hooks";
import { useParentEntityInfo } from "ee/hooks/datasourceEditorHooks";
export const useCurrentEditorState = () => {
const [selectedSegment, setSelectedSegment] = useState<EditorEntityTab>(
@ -58,7 +60,9 @@ export const useCurrentEditorState = () => {
export const useSegmentNavigation = (): {
onSegmentChange: (value: string) => void;
} => {
const basePageId = useSelector(getCurrentBasePageId);
const editorType = useEditorType(location.pathname);
const { parentEntityId: baseParentEntityId } =
useParentEntityInfo(editorType);
/**
* Callback to handle the segment change
@ -70,17 +74,17 @@ export const useSegmentNavigation = (): {
const onSegmentChange = (value: string) => {
switch (value) {
case EditorEntityTab.QUERIES:
history.push(queryListURL({ basePageId }), {
history.push(queryListURL({ baseParentEntityId }), {
invokedBy: NavigationMethod.SegmentControl,
});
break;
case EditorEntityTab.JS:
history.push(jsCollectionListURL({ basePageId }), {
history.push(jsCollectionListURL({ baseParentEntityId }), {
invokedBy: NavigationMethod.SegmentControl,
});
break;
case EditorEntityTab.UI:
history.push(widgetListURL({ basePageId }), {
history.push(widgetListURL({ baseParentEntityId }), {
invokedBy: NavigationMethod.SegmentControl,
});
break;